The AI Has Hands
Ask Crysta — the AI agent on CrystaCode.ai — to switch the site to dark mode, and the site actually turns dark. Ask it to show the login popup, and the modal actually opens. The model doesn't just answer anymore; it operates the UI. But here's the thing: the LLM lives on the server , and the UI lives in the browser . A model can't click buttons. So how do you give a remote brain hands? The answer is a pattern we ended up calling the Client Driver Skill : function calling, with SignalR as the hand. The Problem The first version of our chat was a one-way street. The model could say "sure, I'll take you to the plans page" — and then nothing happened. The answer was text; the UI was deaf. You have two classic options: The client polls the server for commands (ugly, wasteful, feels like 2010) The server pushes commands to the client (real-time, instant, exactly what SignalR is for) We went with the push. The flow became: the model calls a function → the function runs on the server → the server pushes a typed command over SignalR → the client executes it. How It Works (The Full Loop) [Browser] [Server] | | | 1. "switch to dark mode" | | ---- InvokeAsync ---------> | | | 2. Brain runs, model sees | | the UpdateSiteTheme tool | | 3. Model calls the function | | (function calling) | | 4. Push to the exact tab: | <--- ChangeSiteTheme ------ | Clients.Client(connId) | 5. ThemeService flips it | | 6. "Site theme changed..." | 5b. return value re-enters | | the model's context | <--- chat answer ---------- | User types "switch to dark mode" → the Blazor client calls the hub The server session runs the brain; the model sees a tool called UpdateSiteTheme The model decides the user wants dark mode and calls the function The server pushes ChangeSiteTheme(DarkMode) to the exact browser connection The client applies the theme and re-renders The function's return value goes back into the model's context, so the AI knows the theme changed and confirms it in the chat 1) The Client Regist