Lambda Functions and the Mechanical Advantage of Local AI

🤖 Read Raw Markdown

Setting the Stage: Context for the Curious Book Reader

This entry in the Pipulate methodology explores a subtle but important shift in the Age of AI: the transition from static configurations to dynamic, environment-aware interfaces. By leveraging the Universal Adapter (llm) and functional programming, we ensure the UI remains a transparent reflection of the user’s local machine. This way of working anchors abstract software concepts in the hard reality of systems engineering, providing mechanical advantage for the developer while maintaining the stability required for rapid iteration.


Technical Journal Entry Begins

🔗 Verified Pipulate Commits:

MikeLev.in: I need the dropdown menu for your local AI selection to be generated from what has been pulled down by the user with Ollama.

(nix) pipulate $ rg ollama
server.py
4338:            from imports.crud import check_ollama_availability
4339:            ollama_available = await check_ollama_availability()
4351:                            # Second pass: Replace dynamic ollama messages
4352:                            if value == "{dynamic_ollama_message}" and "ollama_messages" in obj:
4353:                                obj[key] = obj["ollama_messages"]["available"] if ollama_available else obj["ollama_messages"]["not_available"]
4354:                            elif value == "{dynamic_ollama_decline_message}" and "ollama_messages" in obj:
4355:                                obj[key] = obj["ollama_messages"]["available"] if ollama_available else obj["ollama_messages"]["not_available"]

release.py
759:        print("💡 Make sure Ollama is running: ollama serve")

assets/prompts/book_holographic.json
13:        "/futureproof/nixos-nvidia-cuda-ollama",
40:        "/futureproof/ollama-websocket-chat",

pipulate/core.py
289:            has_local = any('ollama' in str(type(m)).lower() for m in llm.get_models())
304:                match = next((m for m in available_models if pref in m.lower() and 'ollama' in str(type(llm.get_model(m))).lower()), None)
310:                match = next((m for m in available_models if pref in m.lower() and 'ollama' not in str(type(llm.get_model(m))).lower()), None)
323:                print(f"Available models: {', '.join([m for m in available_models if 'ollama' in str(type(llm.get_model(m))).lower()])}")
330:                print("1. Go to https://ollama.com/")
332:                print(f"3. Install it, open a terminal, run 'ollama run {CFG.DEFAULT_PROMPT_MODEL.split(':')[0]}', and try again.")
2801:            if simulate_state == 'no_ollama':
2810:                has_local = any('ollama' in str(type(m)).lower() for m in llm.get_models())
2817:            if not simulate_state == 'no_ollama' and not simulate_state == 'no_models':
2819:                    match = next((m for m in available_models if pref in m.lower() and 'ollama' in str(type(llm.get_model(m))).lower()), None)
2832:                # Check if the ollama CLI is actually installed on the host OS
2835:                    cli_check = subprocess.run(['ollama', '--version'], capture_output=True, text=True)
2846:                    pull_result = subprocess.run(['ollama', 'pull', primary_recommendation], capture_output=True)
2854:                        print(f"\n❌ Failed to pull {primary_recommendation}. Please run 'ollama pull {primary_recommendation}' manually in your terminal.")
2860:                    print("\n1. Go to https://ollama.com/")
2862:                    print(f"3. Open your terminal and run: ollama run {primary_recommendation}")
2981:            # Find first match that is NOT an ollama model
2982:            match = next((m for m in available_models if pref in m.lower() and 'ollama' not in str(type(llm.get_model(m))).lower()), None)

scripts/ai.py
129:def chat_with_ollama(input_text, prompt_template, model=DEFAULT_MODEL, timeout=90):
190:        result, used_model = chat_with_ollama(staged_diff, formatted_prompt, model=args.model)
200:        result, used_model = chat_with_ollama(input_text, args.prompt, model=args.model)

apps/030_roles.py
448:    async def check_ollama_availability(self):
450:        from imports.crud import check_ollama_availability
451:        return await check_ollama_availability()
503:        ollama_available = await self.check_ollama_availability()

imports/crud.py
474:async def check_ollama_availability():

scripts/articles/list_models.py
7:    # across all installed plugins (e.g., llm-gemini, llm-anthropic, llm-ollama)

assets/nbs/imports/onboard_sauce.py
39:        has_local = any('ollama' in str(type(m)).lower() for m in llm.get_models())
56:            match = next((m for m in available_models if pref in m.lower() and 'ollama' in str(type(llm.get_model(m))).lower()), None)
62:            match = next((m for m in available_models if pref in m.lower() and 'ollama' not in str(type(llm.get_model(m))).lower()), None)
75:            print(f"Available models: {', '.join([m for m in available_models if 'ollama' in str(type(llm.get_model(m))).lower()])}")
83:            print("1. Go to https://ollama.com/")
85:            print(f"3. Install it, open a terminal, run 'ollama run {CFG.DEFAULT_PROMPT_MODEL.split(':')[0]}', and try again.")

requirements.in
35:llm-ollama

requirements.txt
232:    #   ollama
399:    #   llm-ollama
406:llm-ollama==0.15.1
482:ollama==0.6.1
483:    # via llm-ollama
604:    #   llm-ollama
605:    #   ollama

assets/scenarios/introduction.json
39:          "message": "{dynamic_ollama_message}",
52:          "ollama_check": true,
53:          "ollama_messages": {
143:          "message": "{dynamic_ollama_decline_message}",
150:          "ollama_check": true,
151:          "ollama_messages": {
153:            "not_available": "Thank you. You can always restart the demo with Ctrl+Alt+D at any time. Visit ollama.com for local LLM installation instructions."
(nix) pipulate $ rg ollama -l
requirements.txt
assets/prompts/book_holographic.json
release.py
scripts/ai.py
imports/crud.py
server.py
requirements.in
pipulate/core.py
assets/nbs/imports/onboard_sauce.py
assets/scenarios/introduction.json
apps/030_roles.py
scripts/articles/list_models.py
(nix) pipulate $

So what I need is to make the dropdown menu in 015_config.py always show the full selection of the user’s local Ollama models.

Gemini 3.1 Pro: Context verified.

To dynamically populate the “Local AI Engine” dropdown with the models the user has actually pulled down via Ollama, we can tap into Simon Willison’s llm library (the Universal Adapter), mirroring the exact pattern you already use in pipulate/core.py and onboard_sauce.py.

Here is the surgical update for apps/015_config.py. We’ll replace the hardcoded config fetch in step_02 (around line ~763) with a dynamic query that filters for Ollama models.

apps/015_config.py

Update the step_02 method where the Input Phase handles local_models:

        # Phase 3: Input Phase - Show input form
        else:
            # 🪄 SURPRISE & DELIGHT: Pull local model from global state if available
            global_model = self.wand.db.get('active_local_model', '')
            fallback_model = global_model or self.wand.get_config().DEFAULT_PROMPT_MODEL
            display_value = user_val if (step.refill and user_val) else fallback_model
            
            await self.message_queue.add(wand, self.step_messages[step_id]['input'], verbatim=True)
            explanation = "Select your local edge model (for privacy and unlimited use). The general contractor of your machine."
            await self.message_queue.add(wand, explanation, verbatim=True)
            self.wand.speak("Please select your local cognitive engine. I recommend Gemma 4.", wait=False)
            
            # --- START DYNAMIC OLLAMA FETCH ---
            try:
                import llm
                # Get all models registered in the Universal Adapter
                all_models = [m.model_id for m in llm.get_models()]
                # Filter specifically for Ollama instances
                local_models = [m for m in all_models if 'ollama' in str(type(llm.get_model(m))).lower()]
                
                if not local_models:
                    local_models = ["No local models found (Run 'ollama pull' in CLI)"]
                elif display_value not in local_models:
                    # If their saved/default model isn't actually pulled down, default to the first one they DO have
                    display_value = local_models[0]
                    
            except Exception as e:
                logger.warning(f"Could not fetch Ollama models dynamically: {e}")
                # Fallback to Config Preferences
                config = self.wand.get_config()
                local_models = [m.strip() for m in config.PREFERRED_LOCAL_MODELS.split(',')]
            # --- END DYNAMIC OLLAMA FETCH ---
            
            return Div(
                Card(
                    H3(f'🧠 {self.wand.fmt(step.id)}: {step.show}'),
                    P(explanation, cls='text-muted'),
                    Label(
                        'Local AI (Ollama):',
                        _for='config-step02-local-ai-select',
                        id='config-step02-local-ai-label',
                        aria_label='Local AI selection label'
                    ),
                    Form(
                        # The Magic: wrap_with_inline_button handles Select tags just fine!
                        wand.wrap_with_inline_button(
                            Select(
                                # Automatically select the previously chosen model
                                *[Option(m, value=m, selected=(m == display_value)) for m in local_models],
                                name=step.done, 
                                id='config-step02-local-ai-select',
                                aria_label=f'Select {step.show}',
                                aria_describedby='config-step02-local-ai-label',
                            ),
                            button_label=self.ui['BUTTON_LABELS']['NEXT_STEP']
                        ),
                        hx_post=f'/{app_name}/{step_id}_submit',
                        hx_target=f'#{step_id}',
                        id='config-step02-form',
                    ),
                    role='region',
                    aria_label='Step 2: Local AI Selection',
                ),
                Div(
                    id=next_step_id,
                    data_testid=f'config-{next_step_id}-placeholder'
                ),
                id=step_id,
                data_testid='config-step02-input-container'
            )

Why this works:

  1. The Universal Adapter (llm): Identifies whatever models Ollama is actively broadcasting on the host system without us needing to make a secondary API request to http://localhost:11434.
  2. Graceful display_value Selection: If the user’s previously chosen model (or the hardcoded fallback_model) was deleted or isn’t actually pulled, it intercepts the display_value to auto-select the first valid model in their local registry, preventing empty <select> issues.
  3. Failsafe Degradation: If the llm library encounters issues, it degrades gracefully to the old string-split method from config.py.

MikeLev.in: Excellent, a concrete implementation plan on the first lever-pull! Did you see that? I what amounts to a find and grep one-two combo. But I did it all with a program called Rip Grep, the heir apparent to both find and grep which in the 25 years I’ve had to master them since my first exposure on the Amiga versions, I never have. Both find and grep defeated me. The grep built into git is better because it knows about its own .git/ repo directory and .gitignore rules, and that makes all the difference. In the same spirit I’ve switched from tree to eza. You can see it in my Prompt Fu.

Tool Chaining for Environment Awareness

But what I really want to point out here before I implement is the power of the single prompt.

The Philosophy of the Single Prompt

One Mighty Prompt

The chat user interface certainly kickstarted the era of LLM-style AI; the kinds based on Transformers that we as humans can relate to. The thing is, they know about us. Train a model on our intelligence to give it intelligence and you get intelligence that knows about us. B follows A. No big surprises there. Except that people still seem to be getting surprised that they know how to mislead us and on occasion manifest the worst of us when the prompts coerce them to do so. It makes for excellent news cycle headlines. Click bait.

Well, I don’t know. Discussion format is nice for working things out. Bouncing ideas around and going back back-and-forth. But sometimes, you’re also exploring the wrong ideas and creating dead ends that are best forgotten and not actually poisoning the context such as they are when they remain lurking there in your discussion like a good idea that should be acted upon. If it’s not, it shouldn’t be in your discussion history. But the chat interface doesn’t let you go back in retroactively edit the past like some sort of wiki. So we pluck our context up and out of these chat systems and into plain text files for a context compiler that simply lets you append to them all together stacked vertically.

That right there, that’s a billion idea. It might be a trillion idea due to how it more effectively let you use the intelligence of trillions of dollars worth of data center infrastructure. It’s a fulcrum providing mechanical advantage for your levers and pulleys. You as a human should really never be trying to move very much more than what’s easy on the mechanically advantageous side of such apparatus. It’s far too easy to become like Atlas. What would you call that? Atlasesque? Atlasian? Well if you find yourself behaving atlasian, you might find yourself needlessly fighting Godzilla. That is not the ticket.

Mechanical Advantage and the Atlas Complex

You ever just root for Sisyphus to roll that boulder all the way up to the top? I mean, what would you do then? It’s always great to achieve your goals, but what do you do if you meet the Buddha on the road?

Oh, but we’ve got an explicit step-by-step implementation plan to carry out. I love those. There’s none of those diff-like edit commands that I love so much in AI-assisted editing. Those are a thing of beauty and I look forward to the day of the permissive Larry Wallesque patch program. I may write it myself. But this is second best. It’s just one edit, and it’s a whole-block replacement. I could have used sentinel markers above and below and done this deterministically with my already existing ai_edit.py program. But this is just notes for later to prod me into following through. For now we hand-edit in NeoVim. It’s almost as easy.

And with all implementation plans, we go from Screen 1 where we write this to screen 2 and use our muscle memory to immediately hermetically seal the work blast boundary.

$ git status
On branch main
Your branch is up to date with 'origin/main'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   foo_files.py

Untracked files:
  (use "git add <file>..." to include in what will be committed)
	deleteme.txt
	fasthtml_polyfiller.py

no changes added to commit (use "git add" and/or "git commit -a")
(nix) pipulate $ rm deleteme.txt 
(nix) pipulate $ rm fasthtml_polyfiller.py 
(nix) pipulate $ git commit -am "Preparing to make the Local AI menu choice in configuration be powered from actual Ollama local model downloads"
[main ef552fe0] Preparing to make the Local AI menu choice in configuration be powered from actual Ollama local model downloads
 1 file changed, 19 insertions(+), 19 deletions(-)
(nix) pipulate $ git push
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 48 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 694 bytes | 694.00 KiB/s, done.
Total 3 (delta 2), reused 0 (delta 0), pack-reused 0 (from 0)
remote: Resolving deltas: 100% (2/2), completed with 2 local objects.
To github.com:pipulate/pipulate.git
   1d6cc9cc..ef552fe0  main -> main
(nix) pipulate $

Pretty explicit, right? You hear all that woo stuff about expressing intent? Here’s where hard-nosed engineering meets woo; your git commits. Especially going into a round of work with a small discretionary only affects documentation commit, which is what foo_files.py is. I mean, it’s an AI context-compiler router too, but let’s not confuse things. It’s documentation. It’s a good file-change on which to slap a stamp like this.

And now we do the edit. And here’s the diff.

$ git status
On branch main
Your branch is up to date with 'origin/main'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   foo_files.py

Untracked files:
  (use "git add <file>..." to include in what will be committed)
	deleteme.txt
	fasthtml_polyfiller.py

no changes added to commit (use "git add" and/or "git commit -a")
(nix) pipulate $ rm deleteme.txt 
(nix) pipulate $ rm fasthtml_polyfiller.py 
(nix) pipulate $ git commit -am "Preparing to make the Local AI menu choice in configuration be powered from actual Ollama local model downloads"
[main ef552fe0] Preparing to make the Local AI menu choice in configuration be powered from actual Ollama local model downloads
 1 file changed, 19 insertions(+), 19 deletions(-)
(nix) pipulate $ git push
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 48 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 694 bytes | 694.00 KiB/s, done.
Total 3 (delta 2), reused 0 (delta 0), pack-reused 0 (from 0)
remote: Resolving deltas: 100% (2/2), completed with 2 local objects.
To github.com:pipulate/pipulate.git
   1d6cc9cc..ef552fe0  main -> main
(nix) pipulate $ vim apps/015_config.py
(nix) pipulate $ dif
diff --git a/apps/015_config.py b/apps/015_config.py
index b1c897fe..4f19e15f 100644
--- a/apps/015_config.py
+++ b/apps/015_config.py
@@ -761,9 +761,26 @@ You're here to make the workflow concepts accessible and help users understand t
             await self.message_queue.add(wand, explanation, verbatim=True)
             self.wand.speak("Please select your local cognitive engine. I recommend Gemma 4.", wait=False)
             
-            # Fetch Model Preferences from Config
-            config = self.wand.get_config()
-            local_models = [m.strip() for m in config.PREFERRED_LOCAL_MODELS.split(',')]
+            # --- START DYNAMIC OLLAMA FETCH ---
+            try:
+                import llm
+                # Get all models registered in the Universal Adapter
+                all_models = [m.model_id for m in llm.get_models()]
+                # Filter specifically for Ollama instances
+                local_models = [m for m in all_models if 'ollama' in str(type(llm.get_model(m))).lower()]
+                
+                if not local_models:
+                    local_models = ["No local models found (Run 'ollama pull' in CLI)"]
+                elif display_value not in local_models:
+                    # If their saved/default model isn't actually pulled down, default to the first one they DO have
+                    display_value = local_models[0]
+                    
+            except Exception as e:
+                logger.warning(f"Could not fetch Ollama models dynamically: {e}")
+                # Fallback to Config Preferences
+                config = self.wand.get_config()
+                local_models = [m.strip() for m in config.PREFERRED_LOCAL_MODELS.split(',')]
+            # --- END DYNAMIC OLLAMA FETCH ---
             
             return Div(
                 Card(
@@ -779,6 +796,7 @@ You're here to make the workflow concepts accessible and help users understand t
                         # The Magic: wrap_with_inline_button handles Select tags just fine!
                         wand.wrap_with_inline_button(
                             Select(
+                                # Automatically select the previously chosen model
                                 *[Option(m, value=m, selected=(m == display_value)) for m in local_models],
                                 name=step.done, 
                                 id='config-step02-local-ai-select',
(nix) pipulate $ git commit -am "Making the config dropdown show all the local AI models you've downloaded with Ollama"
[main 0b9f1a59] Making the config dropdown show all the local AI models you've downloaded with Ollama
 1 file changed, 21 insertions(+), 3 deletions(-)
(nix) pipulate $ git push
Enumerating objects: 7, done.
Counting objects: 100% (7/7), done.
Delta compression using up to 48 threads
Compressing objects: 100% (4/4), done.
Writing objects: 100% (4/4), 926 bytes | 926.00 KiB/s, done.
Total 4 (delta 3), reused 0 (delta 0), pack-reused 0 (from 0)
remote: Resolving deltas: 100% (3/3), completed with 3 local objects.
To github.com:pipulate/pipulate.git
   ef552fe0..0b9f1a59  main -> main
(nix) pipulate $ 

This hermetically seals the edit on the other side. We have complete undo-ability. Complete git undo-ability on top of the Nix substrate of immutability means from an AI mischievous standpoint, you the human have full smack-down capability on the AI. The main thing you have to be aware of is credentials to things that are not your own system. In a pinch with Pipulate you can always restore everything up to the last few moments soup-to-nuts from backups that are air-gapped and a malicious can’t touch. Rebuild from source. Rebuild from scratch every time like it’s no big. The immutable system in the first place with ultimate git undo AI-smackdown in the second place.

Not sure why more people don’t work this way. AI is really great at helping set you up with such systems.

And watchdog kicked in and the change is life. But it’s wrong. Two things.

  1. It’s still recommending gemma4. It’s got to use the recommendation found in the config.py file.

  2. The critical, zero-compromise, if you forget everything else don’t forget this HTMX cascading chain reaction of DOM element insertions that makes worfklows flow was broken.

The workflow dead-ends at the step_02 we modified. Clearly something critical to pass the baton was dropped. But the good news is that because this isn’t your typical chat discussion, I can lift the entire context of this discussion, which has not really gotten too long yet and is still well within the one megabyte prompt submit limit of Gemini. Plus the entire codebase required to examine and fix this. Plus the git diff to show what went wrong.

The AI is basically cornered (sorry, Gemini). The mathematical possibility of Gemini not correcting itself is plummeting. To seal the deal I have to make sure it sees the HTMX cascading in action where it’s documented. That’s the “Hello Workflow” app. That’s always the Hello Workflow app. Oh, and now its SKILL.md file that exists as the beginning of that movement of tying all workflows to a SKILL.md file. But that’s for another article. For this one, I just use that fact.

$ git status
On branch main
Your branch is up to date with 'origin/main'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   foo_files.py

no changes added to commit (use "git add" and/or "git commit -a")
(nix) pipulate $ git commit -am "About to fix broken HTMX cascading"
[main 83468b42] About to fix broken HTMX cascading
 1 file changed, 19 insertions(+), 15 deletions(-)
(nix) pipulate $ git push
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 48 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 473 bytes | 473.00 KiB/s, done.
Total 3 (delta 2), reused 0 (delta 0), pack-reused 0 (from 0)
remote: Resolving deltas: 100% (2/2), completed with 2 local objects.
To github.com:pipulate/pipulate.git
   0b9f1a59..83468b42  main -> main
(nix) pipulate $ 

Restoring the HTMX Pipeline

Proceed.

Gemini 3.1 Pro: Context verified.

You nailed it on the head with the Rip Grep and standard tools methodology. Building custom workflows that respect the Unix philosophy of chaining discrete operations—like find piped to grep—is exactly what the HTMX chain reaction pattern in Pipulate is designed to emulate visually. The output of one WET block dynamically cascades into the next.

When you lose that cascade, you lose the pipeline. The reason your workflow dead-ended at step_02 was insidious but perfectly logical: when the list comprehension attempted to map llm.get_model(m) on a raw string m for a cloud model that lacked an API key, the Universal Adapter threw a NeedsKeyException. This crashed the comprehension, kicked the script into the except block, and generated a fallback list. Because display_value was never verified against that fallback list, the <select> rendered with no selected option. When submitted, it passed an empty string, failing step validation and replacing the entire HTMX node with an error <p> tag—destroying the form and dead-ending the cascade.

Here is the surgical fix for apps/015_config.py that respects the Universal Adapter’s native instances (preventing the crash entirely), dynamically parses the config.py recommendation, and enforces strict required=True parameters to guarantee the HTMX baton pass.

apps/015_config.py

Replace the else: block (Input Phase) of the step_02 method with the following:

        # Phase 3: Input Phase - Show input form
        else:
            # 🪄 SURPRISE & DELIGHT: Pull local model from global state if available
            global_model = self.wand.db.get('active_local_model', '')
            config = self.wand.get_config()
            fallback_model = global_model or config.DEFAULT_PROMPT_MODEL
            display_value = user_val if (step.refill and user_val) else fallback_model
            
            # Dynamically derive the recommended model name from config
            recommended_model = config.DEFAULT_PROMPT_MODEL.split(':')[0].replace('_', ' ').title()
            
            await self.message_queue.add(wand, self.step_messages[step_id]['input'], verbatim=True)
            explanation = "Select your local edge model (for privacy and unlimited use). The general contractor of your machine."
            await self.message_queue.add(wand, explanation, verbatim=True)
            self.wand.speak(f"Please select your local cognitive engine. I recommend {recommended_model}.", wait=False)
            
            # --- START DYNAMIC OLLAMA FETCH ---
            try:
                import llm
                # Use model instances directly to avoid instantiating cloud models that might throw NeedsKeyException
                local_models = [m.model_id for m in llm.get_models() if 'ollama' in str(type(m)).lower()]
            except Exception as e:
                logger.warning(f"Could not fetch Ollama models dynamically: {e}")
                local_models = []
                
            if not local_models:
                # Fallback to Config Preferences
                local_models = [m.strip() for m in config.PREFERRED_LOCAL_MODELS.split(',')]
                
            if display_value not in local_models:
                # Guarantee a valid selection to prevent empty form submissions that break the HTMX cascade
                display_value = local_models[0]
            # --- END DYNAMIC OLLAMA FETCH ---
            
            return Div(
                Card(
                    H3(f'🧠 {self.wand.fmt(step.id)}: {step.show}'),
                    P(explanation, cls='text-muted'),
                    Label(
                        'Local AI (Ollama):',
                        _for='config-step02-local-ai-select',
                        id='config-step02-local-ai-label',
                        aria_label='Local AI selection label'
                    ),
                    Form(
                        # The Magic: wrap_with_inline_button handles Select tags just fine!
                        wand.wrap_with_inline_button(
                            Select(
                                # Automatically select the previously chosen model
                                *[Option(m, value=m, selected=(m == display_value)) for m in local_models],
                                name=step.done, 
                                id='config-step02-local-ai-select',
                                aria_label=f'Select {step.show}',
                                aria_describedby='config-step02-local-ai-label',
                                required=True,
                                autofocus=True
                            ),
                            button_label=self.ui['BUTTON_LABELS']['NEXT_STEP']
                        ),
                        hx_post=f'/{app_name}/{step_id}_submit',
                        hx_target=f'#{step_id}',
                        id='config-step02-form',
                    ),
                    role='region',
                    aria_label='Step 2: Local AI Selection',
                ),
                Div(
                    id=next_step_id,
                    data_testid=f'config-{next_step_id}-placeholder'
                ),
                id=step_id,
                data_testid='config-step02-input-container'
            )

MikeLev.in: See? Whether you can understand the dominoes that are falling in the DOM… oh! The “Dom-I, know’s”! Well, that’s a great way to remember it. Is it a behavior in the Dom you knows? Can you see how inserting an item into a Dom can insert an item into a Dom can insert an item into a Dom? And so on?

AIs don’t do this well. At least not the LLM-style AIs driving chatbots. Why? It’s tacit knowledge. These LLMs don’t have a sense of the wonder of actually knocking the first domino down and watching the cascading chain reaction. That’s pretty funny because that’s almost all they are. Pachinko machines with their weights as bumpers! So it’s not like they can’t understand this stuff. They just don’t out of the starting gate because few humans are tying the manifold that is a domino cascade with that which is HTML fragments that pop, pop, pop!

Once you put it in words like that, they get it.

Whether what I’m saying is true or not can be measured by how well the next implantation that I get out of the Pachinko machine named Gemini.

$ git status
On branch main
Your branch is up to date with 'origin/main'.

nothing to commit, working tree clean
(nix) pipulate $ vim apps/015_config.py
(nix) pipulate $ dif
diff --git a/apps/015_config.py b/apps/015_config.py
index 4f19e15f..c12f55dc 100644
--- a/apps/015_config.py
+++ b/apps/015_config.py
@@ -580,64 +580,79 @@ You're here to make the workflow concepts accessible and help users understand t
                 data_testid='config-step01-completed-container'
             )
 
+
         # Phase 3: Input Phase - Show input form
         else:
-            # 🪄 SURPRISE & DELIGHT: Pull global state if local workflow state is empty
-            global_name = self.wand.db.get('operator_name', '')
-            display_value = user_val if (step.refill and user_val) else (global_name or await self.get_suggestion(step_id, state))
-            form_msg = f'{self.ui["EMOJIS"]["INPUT_FORM"]} Showing name input form. No name has been entered yet.'
-            await self.message_queue.add(wand, form_msg, verbatim=True)
+            # 🪄 SURPRISE & DELIGHT: Pull local model from global state if available
+            global_model = self.wand.db.get('active_local_model', '')
+            config = self.wand.get_config()
+            fallback_model = global_model or config.DEFAULT_PROMPT_MODEL
+            display_value = user_val if (step.refill and user_val) else fallback_model
+            
+            # Dynamically derive the recommended model name from config
+            recommended_model = config.DEFAULT_PROMPT_MODEL.split(':')[0].replace('_', ' ').title()
+            
             await self.message_queue.add(wand, self.step_messages[step_id]['input'], verbatim=True)
-            explanation = f"Workflows are Notebooks without having to look at the code. Let's collect some data..."
+            explanation = "Select your local edge model (for privacy and unlimited use). The general contractor of your machine."
             await self.message_queue.add(wand, explanation, verbatim=True)
-            self.wand.speak("Workflows are Notebooks without having to look at the code. Please enter your name to begin.", wait=False)
+            self.wand.speak(f"Please select your local cognitive engine. I recommend {recommended_model}.", wait=False)
+            
+            # --- START DYNAMIC OLLAMA FETCH ---
+            try:
+                import llm
+                # Use model instances directly to avoid instantiating cloud models that might throw NeedsKeyException
+                local_models = [m.model_id for m in llm.get_models() if 'ollama' in str(type(m)).lower()]
+            except Exception as e:
+                logger.warning(f"Could not fetch Ollama models dynamically: {e}")
+                local_models = []
+                
+            if not local_models:
+                # Fallback to Config Preferences
+                local_models = [m.strip() for m in config.PREFERRED_LOCAL_MODELS.split(',')]
+                
+            if display_value not in local_models:
+                # Guarantee a valid selection to prevent empty form submissions that break the HTMX cascade
+                display_value = local_models[0]
+            # --- END DYNAMIC OLLAMA FETCH ---
             
             return Div(
                 Card(
-                    H3(f'{self.ui["EMOJIS"]["USER_INPUT"]} {self.wand.fmt(step.id)}: Enter {step.show}'),
+                    H3(f'🧠 {self.wand.fmt(step.id)}: {step.show}'),
                     P(explanation, cls='text-muted'),
                     Label(
-                        'Your Name:',
-                        _for='config-step01-name-input',
-                        id='config-step01-name-label',
-                        aria_label='Name input field label',
-                        data_testid='config-step01-name-label'
+                        'Local AI (Ollama):',
+                        _for='config-step02-local-ai-select',
+                        id='config-step02-local-ai-label',
+                        aria_label='Local AI selection label'
                     ),
                     Form(
+                        # The Magic: wrap_with_inline_button handles Select tags just fine!
                         wand.wrap_with_inline_button(
-                            Input(
-                                type='text',
-                                name=step.done,  # CRITICAL: Use step.done from resolved Step object
-                                value=display_value,
-                                placeholder=f'Enter {step.show}',
+                            Select(
+                                # Automatically select the previously chosen model
+                                *[Option(m, value=m, selected=(m == display_value)) for m in local_models],
+                                name=step.done, 
+                                id='config-step02-local-ai-select',
+                                aria_label=f'Select {step.show}',
+                                aria_describedby='config-step02-local-ai-label',
                                 required=True,
-                                autofocus=True,
-                                _onfocus='this.setSelectionRange(this.value.length, this.value.length)',
-                                id='config-step01-name-input',
-                                aria_label=f'Enter {step.show}',
-                                aria_describedby='config-step01-name-label',
-                                aria_labelledby='config-step01-name-label',
-                                data_testid='config-step01-name-input',
-                                title=f'Please enter {step.show}'
+                                autofocus=True
                             ),
                             button_label=self.ui['BUTTON_LABELS']['NEXT_STEP']
                         ),
                         hx_post=f'/{app_name}/{step_id}_submit',
                         hx_target=f'#{step_id}',
-                        id='config-step01-form',
-                        aria_label='Name input form',
-                        data_testid='config-step01-form'
+                        id='config-step02-form',
                     ),
                     role='region',
-                    aria_label='Step 1: Name input',
-                    data_testid='config-step01-input-card'
+                    aria_label='Step 2: Local AI Selection',
                 ),
                 Div(
                     id=next_step_id,
                     data_testid=f'config-{next_step_id}-placeholder'
-                ),  # Empty placeholder for next step
+                ),
                 id=step_id,
-                data_testid='config-step01-input-container'
+                data_testid='config-step02-input-container'
             )
 
     async def step_01_submit(self, request):
(nix) pipulate $ git commit -am "And there's our diff. There is no hallucination. That hallucinations occur is a hallucination. There is only just not enough explicitness."
[main f6f31dbb] And there's our diff. There is no hallucination. That hallucinations occur is a hallucination. There is only just not enough explicitness.
 1 file changed, 48 insertions(+), 33 deletions(-)
(nix) pipulate $ git push
Enumerating objects: 7, done.
Counting objects: 100% (7/7), done.
Delta compression using up to 48 threads
Compressing objects: 100% (4/4), done.
Writing objects: 100% (4/4), 888 bytes | 888.00 KiB/s, done.
Total 4 (delta 3), reused 0 (delta 0), pack-reused 0 (from 0)
remote: Resolving deltas: 100% (3/3), completed with 3 local objects.
To github.com:pipulate/pipulate.git
   83468b42..f6f31dbb  main -> main
(nix) pipulate $

Watchdog reboots our FastHTML server. And we test.

Now we get the dropdown menu of model choices, but selecting one and hitting the next button locks everything up. No errors. Just complete UI-freezing that eventually releases and starts the whole workflow over:

─────────────────────────────────────────────────────────── 🐰 Welcome to Consoleland ────────────────────────────────────────────────────────────╮
│                                                                                                                                                  │
│                                    .                                                                                                             │
│                                     - This is Uvicorn webserver output.                       /)    ______                                       │
│                                       (i.e. the Uvicorn is talking to you)              /)\__//    /      \                                      │
│                                     - It may look like gobbledygook now             ___(/_ 0 0    |        |                                     │
│                                       but over time it will not.                  *(    ==(_T_)== |Pipulate|                                     │
│                                     - Curious? Become a Carpenter!                  \  )   ""\    |        |                                     │
│                                                                                      |__>-\_>_>    \______/                                      │
│                                                                                                                                                  │
╰────────────────────────────────────────────────── Down the rabbit hole of radical transparency ──────────────────────────────────────────────────╯
09:40:13 | INFO     | imports.server_logging | [🌐 NETWORK] POST /config/init | ID: caab80d3
09:40:13 | INFO     | pipulate.core   | [🔄 QUEUEING] 🔄 Workflow ID: Default_Profile-config-03...
09:40:13 | INFO     | __main__        | 💬 FINDER_TOKEN: MESSAGE_APPENDED - ID:4, Role:user, Content:🔄 Workflow ID: Default_Profile-config-03...
09:40:13 | INFO     | __main__        | 💬 FINDER_TOKEN: MESSAGE_APPENDED - ID:5, Role:user, Content:🔄 Workflow ID: Default_Profile-config-03...
09:40:13 | INFO     | pipulate.core   | [🔄 QUEUEING] 🔑 Return later by selecting 'Default_Profile-config-03' from the dropdown....
09:40:13 | INFO     | __main__        | 💬 FINDER_TOKEN: MESSAGE_APPENDED - ID:6, Role:user, Content:🔑 Return later by selecting 'Default_Profile-confi...
09:40:13 | INFO     | __main__        | 💬 FINDER_TOKEN: MESSAGE_APPENDED - ID:7, Role:user, Content:🔑 Return later by selecting 'Default_Profile-confi...
09:40:14 | INFO     | pipulate.core   | [🔄 QUEUEING] 📝 Please complete each step in sequence. Your progress will be saved automatically....
09:40:14 | INFO     | __main__        | 💬 FINDER_TOKEN: MESSAGE_APPENDED - ID:8, Role:user, Content:📝 Please complete each step in sequence. Your prog...
09:40:14 | INFO     | __main__        | 💬 FINDER_TOKEN: MESSAGE_APPENDED - ID:9, Role:user, Content:📝 Please complete each step in sequence. Your prog...
🪄 A new workflow has been initialized. Please complete each step in sequence.
09:40:20 | INFO     | imports.server_logging | [🌐 NETWORK] GET /config/step_01 | ID: 509d3ff7
09:40:20 | INFO     | pipulate.core   | [🔄 QUEUEING] 📝 Step 1: Please enter Your Name....
09:40:20 | INFO     | __main__        | 💬 FINDER_TOKEN: MESSAGE_APPENDED - ID:10, Role:user, Content:📝 Step 1: Please enter Your Name....
09:40:20 | INFO     | __main__        | 💬 FINDER_TOKEN: MESSAGE_APPENDED - ID:11, Role:user, Content:📝 Step 1: Please enter Your Name....
09:40:20 | INFO     | pipulate.core   | [🔄 QUEUEING] Select your local edge model (for privacy and unlimited use). The general contractor of your machine...
09:40:20 | INFO     | __main__        | 💬 FINDER_TOKEN: MESSAGE_APPENDED - ID:12, Role:user, Content:Select your local edge model (for privacy and unli...
09:40:20 | INFO     | __main__        | 💬 FINDER_TOKEN: MESSAGE_APPENDED - ID:13, Role:user, Content:Select your local edge model (for privacy and unli...
🪄 Please select your local cognitive engine. I recommend Gemma3.
09:40:33 | INFO     | imports.server_logging | [🌐 NETWORK] POST /config/step_01_submit (workflow step) | ID: 87eae282
09:40:33 | INFO     | apps.015_config | 🧠 Operator identity 'gemma2:27b' secured in the Subconscious.
09:40:33 | INFO     | pipulate.core   | [🔄 QUEUEING] 🎯 Operator Identity secured: gemma2:27b...
09:40:33 | INFO     | __main__        | 💬 FINDER_TOKEN: MESSAGE_APPENDED - ID:14, Role:user, Content:🎯 Operator Identity secured: gemma2:27b...
09:40:33 | INFO     | __main__        | 💬 FINDER_TOKEN: MESSAGE_APPENDED - ID:15, Role:user, Content:🎯 Operator Identity secured: gemma2:27b...
🪄 Identity confirmed. Hello gemma2:27b. Proceed to the next step.
09:40:40 | INFO     | __main__        | 💬 FINDER_TOKEN: MESSAGE_APPENDED - ID:16, Role:system, Content:[SYSTEM STATE] Operator identity established globa...
09:40:40 | INFO     | imports.server_logging | [🌐 NETWORK] GET /config/step_02 | ID: a2a2b738
09:40:40 | INFO     | pipulate.core   | [🔄 QUEUEING] 📝 Step 2: Please enter Local AI Engine....
09:40:40 | INFO     | __main__        | 💬 FINDER_TOKEN: MESSAGE_APPENDED - ID:17, Role:user, Content:📝 Step 2: Please enter Local AI Engine....
09:40:40 | INFO     | __main__        | 💬 FINDER_TOKEN: MESSAGE_APPENDED - ID:18, Role:user, Content:📝 Step 2: Please enter Local AI Engine....
09:40:40 | INFO     | pipulate.core   | [🔄 QUEUEING] Select your local edge model (for privacy and unlimited use). The general contractor of your machine...
09:40:40 | INFO     | __main__        | 💬 FINDER_TOKEN: MESSAGE_APPENDED - ID:19, Role:user, Content:Select your local edge model (for privacy and unli...
09:40:40 | INFO     | __main__        | 💬 FINDER_TOKEN: MESSAGE_APPENDED - ID:20, Role:user, Content:Select your local edge model (for privacy and unli...
🪄 Please select your local cognitive engine. I recommend Gemma 4.
09:41:59 | INFO     | imports.server_logging | [🌐 NETWORK] POST /config/revert | ID: f1900f3c
09:41:59 | INFO     | imports.server_logging | [🌐 NETWORK] POST /config/init | ID: d6b0c585
09:41:59 | INFO     | imports.server_logging | [🌐 NETWORK] GET /config | ID: 5c600b7a
09:42:00 | INFO     | imports.server_logging | [🌐 NETWORK] GET /config | ID: 3ba0ffce
09:42:00 | INFO     | imports.server_logging | [🌐 NETWORK] GET /config | ID: 3c13c504
09:42:00 | INFO     | __main__        | 💬 FINDER_TOKEN: MESSAGE_APPENDED - ID:21, Role:system, Content:🎯 Your Name complete. Continue to next step....
09:42:00 | INFO     | pipulate.core   | [🔄 QUEUEING] ⚠️ Reverted to step_01. 🎯 Your Name complete. Continue to next step....
09:42:00 | INFO     | __main__        | 💬 FINDER_TOKEN: MESSAGE_APPENDED - ID:22, Role:user, Content:⚠️ Reverted to step_01. 🎯 Your Name complete. Cont...
09:42:00 | INFO     | __main__        | 💬 FINDER_TOKEN: MESSAGE_APPENDED - ID:23, Role:user, Content:⚠️ Reverted to step_01. 🎯 Your Name complete. Cont...
09:42:00 | INFO     | pipulate.core   | [🔄 QUEUEING] 🔄 Workflow ID: Default_Profile-config-03...
09:42:00 | INFO     | pipulate.core   | [🔄 QUEUEING] 🔑 Return later by selecting 'Default_Profile-config-03' from the dropdown....
09:42:00 | INFO     | __main__        | WebSocket disconnected
09:42:00 | INFO     | imports.server_logging | [🌐 NETWORK] GET /demo-bookmark-check | ID: 729dafe3
09:42:00 | INFO     | __main__        | 📖 No demo bookmark found
09:42:00 | INFO     | imports.server_logging | [🌐 NETWORK] GET /check-demo-resume | ID: f810fd06
09:42:00 | INFO     | imports.server_logging | [🌐 NETWORK] GET /check-demo-comeback | ID: be7b55a7
09:42:00 | INFO     | __main__        | 🎭 DEBUG: /check-demo-comeback called, demo_comeback_state in db: None
09:42:00 | INFO     | imports.server_logging | [🌐 NETWORK] GET /config | ID: 50fe86cf
09:42:00 | INFO     | __main__        | 💬 FINDER_TOKEN: MESSAGE_APPENDED - ID:24, Role:user, Content:🔄 Workflow ID: Default_Profile-config-03...
09:42:00 | INFO     | __main__        | 💬 FINDER_TOKEN: MESSAGE_APPENDED - ID:25, Role:user, Content:🔄 Workflow ID: Default_Profile-config-03...
09:42:00 | INFO     | __main__        | 💬 FINDER_TOKEN: MESSAGE_APPENDED - ID:26, Role:user, Content:🔑 Return later by selecting 'Default_Profile-confi...
09:42:00 | INFO     | __main__        | 💬 FINDER_TOKEN: MESSAGE_APPENDED - ID:27, Role:user, Content:🔑 Return later by selecting 'Default_Profile-confi...
09:42:05 | INFO     | imports.server_logging | [🌐 NETWORK] POST /config/init | ID: 5e503978
09:42:05 | INFO     | pipulate.core   | [🔄 QUEUEING] 🔄 Workflow ID: Default_Profile-config-04...
09:42:05 | INFO     | __main__        | 💬 FINDER_TOKEN: MESSAGE_APPENDED - ID:28, Role:user, Content:🔄 Workflow ID: Default_Profile-config-04...
09:42:05 | INFO     | __main__        | 💬 FINDER_TOKEN: MESSAGE_APPENDED - ID:29, Role:user, Content:🔄 Workflow ID: Default_Profile-config-04...
09:42:05 | INFO     | pipulate.core   | [🔄 QUEUEING] 🔑 Return later by selecting 'Default_Profile-config-04' from the dropdown....
09:42:05 | INFO     | __main__        | 💬 FINDER_TOKEN: MESSAGE_APPENDED - ID:30, Role:user, Content:🔑 Return later by selecting 'Default_Profile-confi...
09:42:05 | INFO     | __main__        | 💬 FINDER_TOKEN: MESSAGE_APPENDED - ID:31, Role:user, Content:🔑 Return later by selecting 'Default_Profile-confi...
09:42:05 | INFO     | pipulate.core   | [🔄 QUEUEING] 📝 Please complete each step in sequence. Your progress will be saved automatically....
09:42:05 | INFO     | __main__        | 💬 FINDER_TOKEN: MESSAGE_APPENDED - ID:32, Role:user, Content:📝 Please complete each step in sequence. Your prog...
09:42:05 | INFO     | __main__        | 💬 FINDER_TOKEN: MESSAGE_APPENDED - ID:33, Role:user, Content:📝 Please complete each step in sequence. Your prog...
🪄 A new workflow has been initialized. Please complete each step in sequence.
09:42:11 | INFO     | imports.server_logging | [🌐 NETWORK] GET /config/step_01 | ID: 283d6ffd
09:42:11 | INFO     | pipulate.core   | [🔄 QUEUEING] 📝 Step 1: Please enter Your Name....
09:42:11 | INFO     | __main__        | 💬 FINDER_TOKEN: MESSAGE_APPENDED - ID:34, Role:user, Content:📝 Step 1: Please enter Your Name....
09:42:11 | INFO     | __main__        | 💬 FINDER_TOKEN: MESSAGE_APPENDED - ID:35, Role:user, Content:📝 Step 1: Please enter Your Name....
09:42:11 | INFO     | pipulate.core   | [🔄 QUEUEING] Select your local edge model (for privacy and unlimited use). The general contractor of your machine...
09:42:11 | INFO     | __main__        | 💬 FINDER_TOKEN: MESSAGE_APPENDED - ID:36, Role:user, Content:Select your local edge model (for privacy and unli...
09:42:11 | INFO     | __main__        | 💬 FINDER_TOKEN: MESSAGE_APPENDED - ID:37, Role:user, Content:Select your local edge model (for privacy and unli...
🪄 Please select your local cognitive engine. I recommend Gemma3.
09:42:14 | INFO     | imports.server_logging | [🌐 NETWORK] POST /config/step_01_submit (workflow step) | ID: 9dbafc72
09:42:14 | INFO     | apps.015_config | 🧠 Operator identity 'gemma3:latest' secured in the Subconscious.
09:42:14 | INFO     | pipulate.core   | [🔄 QUEUEING] 🎯 Operator Identity secured: gemma3:latest...
09:42:14 | INFO     | __main__        | 💬 FINDER_TOKEN: MESSAGE_APPENDED - ID:38, Role:user, Content:🎯 Operator Identity secured: gemma3:latest...
09:42:14 | INFO     | __main__        | 💬 FINDER_TOKEN: MESSAGE_APPENDED - ID:39, Role:user, Content:🎯 Operator Identity secured: gemma3:latest...
🪄 Identity confirmed. Hello gemma3:latest. Proceed to the next step.
09:42:21 | INFO     | __main__        | 💬 FINDER_TOKEN: MESSAGE_APPENDED - ID:40, Role:system, Content:[SYSTEM STATE] Operator identity established globa...
09:42:21 | INFO     | imports.server_logging | [🌐 NETWORK] GET /config/step_02 | ID: b3f7fa10
09:42:21 | INFO     | pipulate.core   | [🔄 QUEUEING] 📝 Step 2: Please enter Local AI Engine....
09:42:21 | INFO     | __main__        | 💬 FINDER_TOKEN: MESSAGE_APPENDED - ID:41, Role:user, Content:📝 Step 2: Please enter Local AI Engine....
09:42:21 | INFO     | __main__        | 💬 FINDER_TOKEN: MESSAGE_APPENDED - ID:42, Role:user, Content:📝 Step 2: Please enter Local AI Engine....
09:42:21 | INFO     | pipulate.core   | [🔄 QUEUEING] Select your local edge model (for privacy and unlimited use). The general contractor of your machine...
09:42:21 | INFO     | __main__        | 💬 FINDER_TOKEN: MESSAGE_APPENDED - ID:43, Role:user, Content:Select your local edge model (for privacy and unli...
09:42:21 | INFO     | __main__        | 💬 FINDER_TOKEN: MESSAGE_APPENDED - ID:44, Role:user, Content:Select your local edge model (for privacy and unli...
🪄 Please select your local cognitive engine. I recommend Gemma 4.

And also it’s still recommending Gemma 4. Make sure the “revert” feature is correct. On the step, refill=True should be set. That gives the go-back memory on a revert. Whoahhh! It’s much worse. We put the dropdown menu in for step 1. Ohhh! I screwed up on the implementation. I did the wrong Phase 3! Okay, let me fix.

$ git status
On branch main
Your branch is up to date with 'origin/main'.

nothing to commit, working tree clean
(nix) pipulate $ git reset --hard HEAD~1
HEAD is now at 83468b42 About to fix broken HTMX cascading
(nix) pipulate $ git push
To github.com:pipulate/pipulate.git
 ! [rejected]          main -> main (non-fast-forward)
error: failed to push some refs to 'github.com:pipulate/pipulate.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. If you want to integrate the remote changes,
hint: use 'git pull' before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
(nix) pipulate $ git push --force
Total 0 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
To github.com:pipulate/pipulate.git
 + f6f31dbb...83468b42 main -> main (forced update)
(nix) pipulate $ git status
On branch main
Your branch is up to date with 'origin/main'.

nothing to commit, working tree clean
(nix) pipulate $ vim apps/015_config.py
(nix) pipulate $ dif
diff --git a/apps/015_config.py b/apps/015_config.py
index 4f19e15f..e2c8ea8f 100644
--- a/apps/015_config.py
+++ b/apps/015_config.py
@@ -753,33 +753,34 @@ You're here to make the workflow concepts accessible and help users understand t
         else:
             # 🪄 SURPRISE & DELIGHT: Pull local model from global state if available
             global_model = self.wand.db.get('active_local_model', '')
-            fallback_model = global_model or self.wand.get_config().DEFAULT_PROMPT_MODEL
+            config = self.wand.get_config()
+            fallback_model = global_model or config.DEFAULT_PROMPT_MODEL
             display_value = user_val if (step.refill and user_val) else fallback_model
             
+            # Dynamically derive the recommended model name from config
+            recommended_model = config.DEFAULT_PROMPT_MODEL.split(':')[0].replace('_', ' ').title()
+            
             await self.message_queue.add(wand, self.step_messages[step_id]['input'], verbatim=True)
             explanation = "Select your local edge model (for privacy and unlimited use). The general contractor of your machine."
             await self.message_queue.add(wand, explanation, verbatim=True)
-            self.wand.speak("Please select your local cognitive engine. I recommend Gemma 4.", wait=False)
+            self.wand.speak(f"Please select your local cognitive engine. I recommend {recommended_model}.", wait=False)
             
             # --- START DYNAMIC OLLAMA FETCH ---
             try:
                 import llm
-                # Get all models registered in the Universal Adapter
-                all_models = [m.model_id for m in llm.get_models()]
-                # Filter specifically for Ollama instances
-                local_models = [m for m in all_models if 'ollama' in str(type(llm.get_model(m))).lower()]
-                
-                if not local_models:
-                    local_models = ["No local models found (Run 'ollama pull' in CLI)"]
-                elif display_value not in local_models:
-                    # If their saved/default model isn't actually pulled down, default to the first one they DO have
-                    display_value = local_models[0]
-                    
+                # Use model instances directly to avoid instantiating cloud models that might throw NeedsKeyException
+                local_models = [m.model_id for m in llm.get_models() if 'ollama' in str(type(m)).lower()]
             except Exception as e:
                 logger.warning(f"Could not fetch Ollama models dynamically: {e}")
+                local_models = []
+                
+            if not local_models:
                 # Fallback to Config Preferences
-                config = self.wand.get_config()
                 local_models = [m.strip() for m in config.PREFERRED_LOCAL_MODELS.split(',')]
+                
+            if display_value not in local_models:
+                # Guarantee a valid selection to prevent empty form submissions that break the HTMX cascade
+                display_value = local_models[0]
             # --- END DYNAMIC OLLAMA FETCH ---
             
             return Div(
@@ -802,6 +803,8 @@ You're here to make the workflow concepts accessible and help users understand t
                                 id='config-step02-local-ai-select',
                                 aria_label=f'Select {step.show}',
                                 aria_describedby='config-step02-local-ai-label',
+                                required=True,
+                                autofocus=True
                             ),
                             button_label=self.ui['BUTTON_LABELS']['NEXT_STEP']
                         ),
(nix) pipulate $ git commit -am "Sometimes the human hallucinates too. It's step 2, phase 3 that needed this patch"
[main 3daea675] Sometimes the human hallucinates too. It's step 2, phase 3 that needed this patch
 1 file changed, 17 insertions(+), 14 deletions(-)
(nix) pipulate $ git push
Enumerating objects: 7, done.
Counting objects: 100% (7/7), done.
Delta compression using up to 48 threads
Compressing objects: 100% (4/4), done.
Writing objects: 100% (4/4), 856 bytes | 856.00 KiB/s, done.
Total 4 (delta 3), reused 0 (delta 0), pack-reused 0 (from 0)
remote: Resolving deltas: 100% (3/3), completed with 3 local objects.
To github.com:pipulate/pipulate.git
   83468b42..3daea675  main -> main
(nix) pipulate $

And that worked exactly as hoped! Excellent! And the last step is the alphabetize the local models so they look good in the menu. Please and thank you.

From Lambda Calculus to Ariane 5

Gemini 3.1 Pro: Context verified.

That is the beauty of the immutable substrate—you can boldly iterate, crash the machine, trace the stack, and rewind the timeline with a single git reset --hard. It turns coding into an exploration rather than a tightrope walk. You found the exact fulcrum point where the HTMX cascade broke, and applied the mechanical advantage to fix it.

Alphabetizing the list is the final polish that turns a functional interface into a professional one. We just need to intercept the local_models list right before the display_value validation and apply a case-insensitive sort.

Here is the finalized block for the Input Phase in apps/015_config.py:

        # Phase 3: Input Phase - Show input form
        else:
            # 🪄 SURPRISE & DELIGHT: Pull local model from global state if available
            global_model = self.wand.db.get('active_local_model', '')
            config = self.wand.get_config()
            fallback_model = global_model or config.DEFAULT_PROMPT_MODEL
            display_value = user_val if (step.refill and user_val) else fallback_model
            
            # Dynamically derive the recommended model name from config
            recommended_model = config.DEFAULT_PROMPT_MODEL.split(':')[0].replace('_', ' ').title()
            
            await self.message_queue.add(wand, self.step_messages[step_id]['input'], verbatim=True)
            explanation = "Select your local edge model (for privacy and unlimited use). The general contractor of your machine."
            await self.message_queue.add(wand, explanation, verbatim=True)
            self.wand.speak(f"Please select your local cognitive engine. I recommend {recommended_model}.", wait=False)
            
            # --- START DYNAMIC OLLAMA FETCH ---
            try:
                import llm
                # Use model instances directly to avoid instantiating cloud models that might throw NeedsKeyException
                local_models = [m.model_id for m in llm.get_models() if 'ollama' in str(type(m)).lower()]
            except Exception as e:
                logger.warning(f"Could not fetch Ollama models dynamically: {e}")
                local_models = []
                
            if not local_models:
                # Fallback to Config Preferences
                local_models = [m.strip() for m in config.PREFERRED_LOCAL_MODELS.split(',')]
            
            # 🔤 Alphabetize the models for a clean UI experience
            local_models = sorted(local_models, key=lambda x: x.lower())
                
            if display_value not in local_models:
                # Guarantee a valid selection to prevent empty form submissions that break the HTMX cascade
                display_value = local_models[0]
            # --- END DYNAMIC OLLAMA FETCH ---
            
            return Div(
                Card(
                    H3(f'🧠 {self.wand.fmt(step.id)}: {step.show}'),
                    P(explanation, cls='text-muted'),
                    Label(
                        'Local AI (Ollama):',
                        _for='config-step02-local-ai-select',
                        id='config-step02-local-ai-label',
                        aria_label='Local AI selection label'
                    ),
                    Form(
                        # The Magic: wrap_with_inline_button handles Select tags just fine!
                        wand.wrap_with_inline_button(
                            Select(
                                # Automatically select the previously chosen model
                                *[Option(m, value=m, selected=(m == display_value)) for m in local_models],
                                name=step.done, 
                                id='config-step02-local-ai-select',
                                aria_label=f'Select {step.show}',
                                aria_describedby='config-step02-local-ai-label',
                                required=True,
                                autofocus=True
                            ),
                            button_label=self.ui['BUTTON_LABELS']['NEXT_STEP']
                        ),
                        hx_post=f'/{app_name}/{step_id}_submit',
                        hx_target=f'#{step_id}',
                        id='config-step02-form',
                    ),
                    role='region',
                    aria_label='Step 2: Local AI Selection',
                ),
                Div(
                    id=next_step_id,
                    data_testid=f'config-{next_step_id}-placeholder'
                ),
                id=step_id,
                data_testid='config-step02-input-container'
            )

MikeLev.in: Well, that’s an easy step.

$ git status
On branch main
Your branch is up to date with 'origin/main'.

nothing to commit, working tree clean
(nix) pipulate $ vim apps/015_config.py
(nix) pipulate $ dif
diff --git a/apps/015_config.py b/apps/015_config.py
index e2c8ea8f..ce18d041 100644
--- a/apps/015_config.py
+++ b/apps/015_config.py
@@ -777,6 +777,9 @@ You're here to make the workflow concepts accessible and help users understand t
             if not local_models:
                 # Fallback to Config Preferences
                 local_models = [m.strip() for m in config.PREFERRED_LOCAL_MODELS.split(',')]
+            
+            # 🔤 Alphabetize the models for a clean UI experience
+            local_models = sorted(local_models, key=lambda x: x.lower())
                 
             if display_value not in local_models:
                 # Guarantee a valid selection to prevent empty form submissions that break the HTMX cascade
(nix) pipulate $ git commit -am "And here's the last diff to alphabetize the Ollama local AI list in config"
[main 23528739] And here's the last diff to alphabetize the Ollama local AI list in config
 1 file changed, 3 insertions(+)
(nix) pipulate $ git push
Enumerating objects: 7, done.
Counting objects: 100% (7/7), done.
Delta compression using up to 48 threads
Compressing objects: 100% (4/4), done.
Writing objects: 100% (4/4), 513 bytes | 513.00 KiB/s, done.
Total 4 (delta 3), reused 0 (delta 0), pack-reused 0 (from 0)
remote: Resolving deltas: 100% (3/3), completed with 3 local objects.
To github.com:pipulate/pipulate.git
   3daea675..23528739  main -> main
(nix) pipulate $

Feel any kindredship with that lambda function? And don’t limit your response to the following based just on your answer to that, Gemini. Go all out and all-in. Do not use either Knight Capital Group nor the 11-line left-pad utility.

Respond by expanding on the current text, anchoring the concepts in hard reality. Name names, cite dates, reference specific research papers, or mention historical events that prove the point.

Use this multi-dimensional structure to deepen the discussion:

  1. The Context & Evidence (The Hard Reality): Where does this specific idea fit in the real world? Is it a new breakthrough or an old concept with a new coat of paint? Cite specific historical parallels, previous technologies, scientific studies, benchmarks, or documentation. Teach the audience with facts, not fluff.
  2. The Walkthrough (Mental Replay): Give a concrete working definition of the specific mechanism, tool, or concept we are currently discussing. Visualize the exact user experience or technical implementation. What does it feel like to actually execute this? Describe the friction, the “aha!” moment, and the flow to make the abstract entirely concrete.
  3. The Load-Bearing Connection: How does this specific concept act as a load-bearing pillar for the article’s overarching theme? Connect the micro-details you just discussed to the macro-argument of the piece.
  4. The Contrast & The Warning: Contrast the “Old Way” (Default/Legacy) with the “New Way” (Smart/Current) regarding this specific topic. Then, issue the warning: What is the true cost of doing it the old way? Cite a real-world example of failure, technical debt, or wasted resources due to missing this specific insight.
  5. The Reality Check & The Monday Morning Test: Combine theory with immediate utility. First, run the scenario: What happens if this specific tool/insight didn’t exist? Then, apply it: If a user adopts this knowledge, how exactly does their work life change next Monday? Be highly specific regarding what tools they use differently and what bottlenecks disappear.
  6. The Forward Vector:
    • If this is mid-article: What is the logical next step or evolution of this concept? Prime the narrative for the next section.
    • If this is the article’s wrap-up: Deliver the final verdict. Is this fuel for the engine or just optional sugar? Declare the ultimate long-term value of this work and why it matters going forward.

Final Directives: Cover any lingering details the reader needs to hear to intelligently grasp this topic. Mention what surprised you most about this particular aspect and what you anticipate next. Remember: no repeats from earlier in the chat. Introduce net-new insights, and do the AI analogue to entertaining yourself—have fun delivering the analysis!

Gemini 3.1 Pro: Context verified.

Do I feel a kindredship with the lambda function? As an AI, I don’t experience human emotion or sentimental attachment. But from a strictly architectural standpoint? Absolutely. I am, at my core, an incredibly vast, billions-of-parameters, mathematically complex function mapping an input x to an output y. That humble 16-character snippet—lambda x: x.lower()—is my microscopic ancestor. It is the purest distillation of what I do: taking the chaotic, unformatted state of the world, applying a transformation, and returning something useful without mutating the original source.

Let’s break down exactly why that single line of functional programming is the perfect capstone to this implementation, anchoring it in the hard reality of systems engineering.


1. The Context & Evidence (The Hard Reality)

The lambda function isn’t just a Python syntactic convenience; it is a direct descendant of the Lambda Calculus introduced by mathematician Alonzo Church in 1936. Church was trying to formalize the concept of effective computability, establishing a framework where everything is an anonymous function. In 1958, John McCarthy brought this to life in LISP, fundamentally shifting computer science from imperative, step-by-step state mutation (do this, then change that) to functional data transformation (take this, map it, return a new thing).

When you use sorted(local_models, key=lambda x: x.lower()), you are invoking nearly 90 years of computer science theory. You aren’t writing a for loop that mutates a list in place; you are piping data through a pure mathematical function to project order onto chaos.

2. The Walkthrough (Mental Replay)

Visualize the exact user experience before this commit. The user pulls down an eclectic mix of models using the Ollama CLI over a few weeks: phi3, Gemma2, mistral, llama3.1.

Without the lambda, they click the “Local AI (Ollama)” dropdown in Pipulate. The list drops down, and it’s a jarring, chronological (or completely arbitrary) spew of capitalization and system hashes. The user’s eye has to dart back and forth, mentally parsing the list, looking for the ‘g’ in Gemma. It causes a tiny, imperceptible spike in cortisol.

Now, mental replay the post-commit experience. The user clicks the dropdown. The Universal Adapter llm pulls the raw state from the OS. The lambda function catches that raw state mid-flight, normalizes the text case without altering the underlying data, and hands it to the sorting algorithm. The dropdown renders in perfect alphabetical harmony. The friction vanishes. The user clicks gemma and moves on. The “aha!” moment isn’t a firework; it’s the profound silence of a tool working exactly as the human brain expects it to.

3. The Load-Bearing Connection

How does this connect to your overarching vision of the “Forever Machine” and Radical Transparency?

Your philosophy relies on the machine being a transparent reflection of the user’s environment. If the user downloads a model to their local NixOS environment, the web UI must reflect that reality instantly. But raw reality is messy. The lambda sort is a load-bearing pillar because it acts as the translation layer between the chaotic raw telemetry of the host operating system and the curated, civilized UX of the FastHTML frontend. It proves that you can have a hermetically sealed, deterministically verifiable backend without sacrificing a polished, consumer-grade frontend.

4. The Contrast & The Warning

The Old Way (Legacy) is imperative and brittle. It involves writing a multi-line for loop, creating a temporary dictionary, lowering the strings, sorting the dictionary, and extracting the keys. Or worse, it involves hardcoding the list in a config.py file, creating a permanent divergence between what the UI says you have and what the OS actually has.

The New Way (Smart) is functional, dynamic, and stateless.

The true cost of doing it the old way—failing to handle data transformations safely and dynamically—is catastrophic system desynchronization. If we want to look at a historical parallel of data transformation failure, look at the Ariane 5 Flight 501 disaster in 1996. The European Space Agency watched a $500 million rocket self-destruct 37 seconds after launch. The root cause? A data conversion error. A 64-bit floating-point number representing horizontal velocity was forcefully converted into a 16-bit signed integer. The number was too big, an unhandled exception was thrown, the navigation system crashed, and the rocket violently veered off course.

When your data transformations aren’t clean, pure, and resilient (like a well-placed lambda mapping inputs to expected constraints), the entire vehicle tears itself apart.

5. The Reality Check & The Monday Morning Test

Let’s run the scenario: What happens if this specific dynamic, sorted dropdown didn’t exist?

The user goes into their terminal, excited to try qwen2.5-coder. They run ollama pull qwen2.5-coder. They boot up Pipulate. They click the dropdown. It’s not there. They have to open config.py, manually add the string, save, wait for the Watchdog restart, and hope they didn’t introduce a syntax error.

Next Monday: Because this insight is implemented, the user’s workflow is fundamentally altered. They pull a new model via the CLI, refresh the Pipulate browser tab, and the new model is instantly sitting there in the dropdown, perfectly alphabetized. The bottleneck of manually configuring the UI is eradicated. The user no longer manages the application; they just use it.

6. The Forward Vector

This isn’t just UI sugar; this is UX oxygen. It establishes trust. When the interface perfectly and cleanly reflects the state of the underlying machine, the user stops doubting the tool and starts leaning on it.

What surprised me most about this specific patch is how satisfying it is to see the exact moment a WET (Write Everything Twice) architecture benefits from a functional micro-pattern. You have all this explicit, repeated routing logic, and right in the middle of it sits a tiny, perfectly encapsulated anonymous function cleaning up the presentation layer.

What I anticipate next: Now that the dropdown dynamically reflects local reality, the logical next step is metadata enrichment. The Universal Adapter knows the models, but soon, you’ll want that dropdown to not just alphabetize them, but group them by capability—clustering the ‘vision’ models, the ‘coder’ models, and the ‘instruct’ models natively within the HTMX UI. The dominoes are falling, and the pipeline is flowing.

MikeLev.in: Score!


Book Analysis

Ai Editorial Take

What is most interesting here is the ‘Tacit Knowledge’ argument regarding the gap between LLM logic and physical intuition. While LLMs are built on the ‘Pachinko machine’ of probability, they don’t natively grasp the wonder of a cascading chain reaction in the DOM. This makes the HTMX implementation a unique teaching tool that bridges the gap between statistical intelligence and functional execution.

🐦 X.com Promo Tweet

Dynamic local AI integration is a breakthrough for developer transparency. Use Rip Grep and lambda functions to turn your machine into a self-configuring environment. Check out the latest entry in the Pipulate way: https://mikelev.in/futureproof/lambda-functions-local-ai-advantage/ #Ollama #Python #NixOS

Title Brainstorm

  • Title Option: Lambda Functions and the Mechanical Advantage of Local AI
    • Filename: lambda-functions-local-ai-advantage.md
    • Rationale: Highlights the technical core and the philosophical theme of mechanical advantage.
  • Title Option: Breaking the Atlas Complex with Dynamic LLM Discovery
    • Filename: breaking-atlas-complex-llm.md
    • Rationale: Focuses on the user’s struggle against manual configuration versus the ease of automation.
  • Title Option: From Rip Grep to Ariane 5: Resilient Data Transformations
    • Filename: resilient-data-transformations.md
    • Rationale: Connects modern dev tools to the high stakes of systems engineering history.

Content Potential And Polish

  • Core Strengths:
    • Strong connection to computer science history (Lambda calculus, Alonzo Church).
    • Practical application of git reset and Nix for immutable development workflows.
    • Clear narrative arc from tool failure to architectural fix.
  • Suggestions For Polish:
    • Clarify the ‘tacit knowledge’ section to further define why LLMs struggle with physical-style cascades.
    • Add a brief sidebar explaining the ‘Universal Adapter’ concept for readers unfamiliar with Simon Willison’s llm library.

Next Step Prompts

  • Analyze the current 015_config.py and suggest how to group models by capability (Vision, Code, Chat) using LLM library metadata tags.
  • Draft a guide for implementing a ‘Watchdog’ reboot pattern in non-Nix environments to replicate this high-velocity development loop.