<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: vishalmysore</title>
    <description>The latest articles on DEV Community by vishalmysore (@vishalmysore).</description>
    <link>https://dev.to/vishalmysore</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1386010%2F83aba423-ebfc-46df-8819-a0de1d1e8075.jpeg</url>
      <title>DEV Community: vishalmysore</title>
      <link>https://dev.to/vishalmysore</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/vishalmysore"/>
    <language>en</language>
    <item>
      <title>PROOF for AI Agents: A Technical Scoring Rubric For Self Evaluation</title>
      <dc:creator>vishalmysore</dc:creator>
      <pubDate>Wed, 19 Aug 2026 20:17:47 +0000</pubDate>
      <link>https://dev.to/vishalmysore/proof-for-ai-agents-a-technical-scoring-rubric-for-self-evaluation-3896</link>
      <guid>https://dev.to/vishalmysore/proof-for-ai-agents-a-technical-scoring-rubric-for-self-evaluation-3896</guid>
      <description>&lt;p&gt;PROOF — Planning, Reasoning, Orchestration, Observability, Feedback — is a five-category, 25-point rubric for scoring whether an "AI agent" claim is actually backed by agentic architecture. The version below breaks each category into measurable sub-criteria instead of a single subjective 0–5 gut call, and applies it to two domains where the difference between a real agent and a well-dressed prompt isn't just an ROI question — it's a safety and liability question: healthcare and manufacturing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Rubric, Formalized
&lt;/h2&gt;

&lt;p&gt;Each category is worth 5 points, built from concrete, checkable sub-criteria rather than a single impression score.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Planning (5 pts)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Explicit goal representation and task decomposition artifact exists — a plan graph, task tree, or structured intermediate representation, not just a prompt (0–2)&lt;/li&gt;
&lt;li&gt;System can revise the plan mid-execution when new information invalidates the original approach, rather than executing a fixed script (0–2)&lt;/li&gt;
&lt;li&gt;Constraint handling prunes invalid or unsafe plan branches before execution, not after (0–1)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Reasoning (5 pts)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Outputs are grounded in retrievable evidence — cited tool output, sensor data, or source documents — not just fluent text (0–2)&lt;/li&gt;
&lt;li&gt;A verification or self-consistency pass exists (a second model, a rule-based checker, or a cross-reference step) before the reasoning is acted on (0–2)&lt;/li&gt;
&lt;li&gt;The system can abstain or escalate under uncertainty instead of producing a confident guess (0–1)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Orchestration (5 pts)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Coordination runs on an explicit state machine or durable workflow engine (LangGraph, Temporal, a formal state graph), not ad hoc prompt chaining (0–2)&lt;/li&gt;
&lt;li&gt;Each step has defined retry, rollback, and failure-handling behavior — not "if it errors, stop" (0–2)&lt;/li&gt;
&lt;li&gt;Sub-agents or stages communicate through typed, validated interfaces (schemas), not free-text handoffs (0–1)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Observability (5 pts)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Per-step tracing — tool calls, latency, token/compute cost — is exported to a real system (OpenTelemetry, a trace store, a cost dashboard), not just visible in a terminal (0–2)&lt;/li&gt;
&lt;li&gt;An immutable, exportable audit log exists, sufficient for a third party to reconstruct what happened and why (0–2)&lt;/li&gt;
&lt;li&gt;Cost and usage are attributed per task or per case, not just aggregated monthly (0–1)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Feedback (5 pts)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An automated evaluation suite (golden test set, regression tests) runs pre-deployment and continuously, not just once at launch (0–2)&lt;/li&gt;
&lt;li&gt;Irreversible or high-consequence actions are gated behind a human checkpoint, not auto-executed (0–2)&lt;/li&gt;
&lt;li&gt;Flagged failures feed back into the system — an updated eval, a corrected prompt, a retraining trigger — closing the loop rather than being logged and forgotten (0–1)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Healthcare: Clinical Intake and Triage Support
&lt;/h2&gt;

&lt;p&gt;Two versions of the same idea — an agent that reviews patient intake data and flags cases for clinician attention — scored against the same rubric.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Version A — the thin wrapper.&lt;/strong&gt; A single LLM call takes free-text intake notes and outputs a "risk level" and a suggested next step, displayed directly to a nurse with no supporting detail.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Score&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Planning&lt;/td&gt;
&lt;td&gt;1/5&lt;/td&gt;
&lt;td&gt;One-shot classification, no decomposition, no revision if new labs come in mid-session&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reasoning&lt;/td&gt;
&lt;td&gt;1/5&lt;/td&gt;
&lt;td&gt;Risk label isn't traceable to specific findings; asking "why" gets a plausible-sounding paragraph, not a cited data point&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Orchestration&lt;/td&gt;
&lt;td&gt;0/5&lt;/td&gt;
&lt;td&gt;No pipeline — a single call in, a single output out&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Observability&lt;/td&gt;
&lt;td&gt;0/5&lt;/td&gt;
&lt;td&gt;No trace of which fields the model weighted, no audit trail beyond the final label&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Feedback&lt;/td&gt;
&lt;td&gt;1/5&lt;/td&gt;
&lt;td&gt;No eval suite; "verification" is whether the nurse happens to disagree and overrides it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;3/25&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Bagent&lt;/strong&gt; — regardless of the clinical framing, this is a single unverified LLM call with real-world consequences riding on it&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Version B — the engineered version.&lt;/strong&gt; Structured intake fields feed a decomposition step that checks against defined clinical decision rules and known red-flag combinations; every flagged risk factor links to the specific vital sign, lab value, or reported symptom that triggered it; the pipeline runs on a durable workflow engine with a retry policy per data source; every run is traced and logged to an audit store built for compliance review; nothing is escalated to "urgent" without a rule-based checker agreeing with the model's assessment, and any case the model is uncertain about is auto-routed to a clinician rather than guessed at; a continuously-run eval suite checks the system against a held-out set of historical, clinician-labeled cases before every deployment.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Score&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Planning&lt;/td&gt;
&lt;td&gt;4/5&lt;/td&gt;
&lt;td&gt;Structured decomposition against defined criteria; revises assessment as new intake data arrives&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reasoning&lt;/td&gt;
&lt;td&gt;4/5&lt;/td&gt;
&lt;td&gt;Every flag traces to a specific data point; uncertain cases are surfaced, not guessed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Orchestration&lt;/td&gt;
&lt;td&gt;5/5&lt;/td&gt;
&lt;td&gt;Durable workflow engine, explicit retry/rollback per data source, typed interfaces between stages&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Observability&lt;/td&gt;
&lt;td&gt;4/5&lt;/td&gt;
&lt;td&gt;Full per-step tracing and an audit-grade log; cost/latency attribution present&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Feedback&lt;/td&gt;
&lt;td&gt;5/5&lt;/td&gt;
&lt;td&gt;Continuous eval against clinician-labeled cases, hard checkpoint before any "urgent" escalation, closed-loop correction when a miss is found&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;22/25&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Real Agent&lt;/strong&gt; — genuinely agentic, and just as importantly, built so a human can audit exactly why it did what it did&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The gap between these two isn't model quality — both could run on the same underlying LLM. It's whether Reasoning is traceable and whether Feedback includes a hard gate before anything irreversible happens. In a clinical setting, those are exactly the two categories liability actually hinges on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Manufacturing: Production-Line Anomaly Response
&lt;/h2&gt;

&lt;p&gt;Same exercise, applied to an agent monitoring sensor data on a production line and responding to anomalies — everything from a temperature drift to a full line stoppage.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Version A — the thin wrapper.&lt;/strong&gt; A model watches a stream of sensor readings, and when something looks off, it calls an API to adjust a setpoint or halt the line, based on a prompt describing "normal" operating ranges.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Score&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Planning&lt;/td&gt;
&lt;td&gt;1/5&lt;/td&gt;
&lt;td&gt;Reacts to the latest reading; no model of what "normal drift" vs. "genuine fault" looks like over time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reasoning&lt;/td&gt;
&lt;td&gt;1/5&lt;/td&gt;
&lt;td&gt;No grounding beyond the instantaneous reading — no correlation against related sensors, maintenance history, or known fault signatures&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Orchestration&lt;/td&gt;
&lt;td&gt;1/5&lt;/td&gt;
&lt;td&gt;Single call triggers a single action; no defined behavior if the halt command itself fails to execute&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Observability&lt;/td&gt;
&lt;td&gt;1/5&lt;/td&gt;
&lt;td&gt;Action gets logged as an event, but no trace of what data or reasoning triggered it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Feedback&lt;/td&gt;
&lt;td&gt;0/5&lt;/td&gt;
&lt;td&gt;No eval against historical fault data; nothing checks whether the halt call was even the right call after the fact&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;4/25&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Bagent&lt;/strong&gt; — a live model wired directly to a physical actuator with no verification step is a hazard, not an agent&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Version B — the engineered version.&lt;/strong&gt; Sensor streams feed a state model that distinguishes normal drift from anomaly using multi-sensor correlation and historical baselines; any proposed corrective action is checked against interlock and safety constraints before execution; the workflow runs on an orchestration layer with explicit rollback if an actuator command fails to confirm; every decision — inputs, correlated signals, the action taken, and the interlock check result — is traced to a time-series log tied to the plant's existing OPC-UA/historian infrastructure; low-confidence anomalies alert a human operator instead of triggering an automatic response, and full line stoppages always require operator confirmation regardless of model confidence; the fault-detection model is continuously evaluated against a labeled library of past fault events and re-validated whenever a new fault type is added.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Score&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Planning&lt;/td&gt;
&lt;td&gt;4/5&lt;/td&gt;
&lt;td&gt;Multi-sensor correlation against historical baselines, not single-reading reaction&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reasoning&lt;/td&gt;
&lt;td&gt;4/5&lt;/td&gt;
&lt;td&gt;Every action traces to specific correlated signals; low-confidence cases don't get auto-actioned&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Orchestration&lt;/td&gt;
&lt;td&gt;5/5&lt;/td&gt;
&lt;td&gt;Explicit interlock checks, confirmed rollback on failed actuator commands&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Observability&lt;/td&gt;
&lt;td&gt;5/5&lt;/td&gt;
&lt;td&gt;Full decision trace tied into existing historian infrastructure — auditable by plant engineers, not just data scientists&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Feedback&lt;/td&gt;
&lt;td&gt;4/5&lt;/td&gt;
&lt;td&gt;Continuous evaluation against a labeled fault library; hard human gate on full stoppages&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;22/25&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Real Agent&lt;/strong&gt; — same category of task as Version A, built so a wrong call gets caught before it touches a physical process&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  What the Two Domains Have in Common
&lt;/h2&gt;

&lt;p&gt;Line up both Version B scorecards and the pattern holds across completely different domains: the categories that separate a real agent from a dangerous wrapper aren't Planning or Orchestration — plenty of thin wrappers can fake a plausible-looking pipeline. It's Reasoning traceability and the Feedback hard-gate. In both healthcare and manufacturing, the real agent's defining trait isn't that it's smarter — it's that every consequential action is grounded in a specific, checkable piece of evidence, and nothing irreversible happens without either a rule-based check or a human confirming it first.&lt;/p&gt;

&lt;p&gt;That's the actual engineering takeaway for anyone scoring an agent in a regulated or safety-critical domain: don't spend your review time asking whether the plan looks sophisticated. Spend it asking to see the trace for the last high-stakes decision it made, and asking who — or what — had to sign off before it acted.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Note: the scenarios above are illustrative composites built to demonstrate the scoring method, not descriptions of any specific deployed product. Nothing here is medical, safety, or compliance advice — treat it as an engineering evaluation framework, not a certification standard.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>architecture</category>
      <category>llm</category>
    </item>
    <item>
      <title>Is This a Real AI Agent? Ask for PROOF</title>
      <dc:creator>vishalmysore</dc:creator>
      <pubDate>Wed, 19 Aug 2026 19:55:24 +0000</pubDate>
      <link>https://dev.to/vishalmysore/is-this-a-real-ai-agent-ask-for-proof-30in</link>
      <guid>https://dev.to/vishalmysore/is-this-a-real-ai-agent-ask-for-proof-30in</guid>
      <description>&lt;p&gt;&lt;strong&gt;Every other post on the AI and engineering subreddits this year has an "AI agent" in it somewhere. Most of them can't survive one question: where's the PROOF?&lt;/strong&gt; That's not rhetorical — PROOF is a five-part checklist you can actually run against any agentic AI claim, live, in a thread, a standup, or a demo, before anyone signs off on it.&lt;/p&gt;

&lt;p&gt;The word "agent" has become one of the most overused terms in software, and the threads where practicing engineers actually compare notes — r/ExperiencedDevs, r/developersIndia, and similar corners of the internet — keep circling back to the same complaint. It's applied to everything from genuinely autonomous systems that plan, act, and self-correct with minimal supervision, to a single prompt with a role attached and a rebrand. The gap between those two things is enormous — in engineering complexity, in reliability, and most of all, in cost. And because the word itself has stopped meaning anything specific, most people evaluating an "agent" have no consistent way to tell which one they're actually looking at.&lt;/p&gt;

&lt;p&gt;PROOF fixes that. It's five questions — Planning, Reasoning, Orchestration, Observability, Feedback — and a simple scoring method that turns "does this feel agentic?" into something you can actually answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why "Agent" Stopped Meaning Anything
&lt;/h2&gt;

&lt;p&gt;Part of the problem is that building something that looks agentic in a demo is easy. Wire a large language model to a couple of tools, give it a system prompt describing a role, chain two or three calls together, and you have something that can pass for "autonomous" in a five-minute walkthrough. It answers questions, it calls an API, it produces a plausible-looking output. None of that requires the model to actually plan, verify its own work, or operate safely without someone watching every step.&lt;/p&gt;

&lt;p&gt;The trouble starts when that demo gets scaled into production without anyone checking whether the underlying capability was ever really there. A system that looks impressive answering ten curated questions can fall apart completely on the eleventh, because there was never any planning logic to generalize from — just a good prompt and a lucky run. Multiply that by real budgets, real customer data, and real deadlines, and the gap between "looked agentic" and "was agentic" becomes an expensive one to discover after the fact.&lt;/p&gt;

&lt;h2&gt;
  
  
  The PROOF Framework
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Planning.&lt;/strong&gt; A real agent decomposes a goal into steps before it acts — it doesn't just react to the next input in a fixed sequence. Ask: &lt;em&gt;show me a case where it chose a different approach than the obvious first guess, because the situation called for it.&lt;/em&gt; If every run follows the same script regardless of context, there's no planning happening — just execution of something a human already decided.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reasoning.&lt;/strong&gt; A real agent can justify a decision in a way that's checkable against something outside its own output — a log, a source document, a test result — not just a confident-sounding explanation. Ask: &lt;em&gt;make it prove a specific claim using evidence, not memory.&lt;/em&gt; Watch what happens when it can't find that evidence. A system with real reasoning will say so. One without it will often just make something up that sounds right.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Orchestration.&lt;/strong&gt; This is about whether there's a real structure coordinating multiple steps — state passed deliberately between stages, defined roles, handled failure — or whether it's one prompt calling another prompt because someone strung two API calls together and called it a pipeline. Ask: &lt;em&gt;what happens to the rest of the process if one step partially fails?&lt;/em&gt; A vague answer, or "it just continues," is a sign there's no real orchestration layer underneath the marketing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Observability.&lt;/strong&gt; Can someone actually see what the system is doing while it's doing it — which tools it called, what each step cost, what it considered and rejected — or does it hand you a finished result with no visibility into how it got there? Ask to see the dashboard, not the demo. A system with nothing to show here isn't necessarily broken, but it is a black box, and black boxes are exactly where expensive mistakes hide until they've already happened.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Feedback.&lt;/strong&gt; Does the system have a real loop that catches its own mistakes — automated evaluations, tests, structured human checkpoints — or does "verification" mean someone skimmed the output and it seemed fine? Ask directly: &lt;em&gt;what caught the last time this was wrong, and how?&lt;/em&gt; If nobody has a specific answer to that question, there's no feedback loop, just hope.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scoring It
&lt;/h2&gt;

&lt;p&gt;Score each of the five categories from 0 to 5 based on specific, checkable evidence — not on how confident the answer sounded. Twenty-five points total.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Score&lt;/th&gt;
&lt;th&gt;What You're Actually Looking At&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;20–25&lt;/td&gt;
&lt;td&gt;A genuinely agentic system — autonomy, planning, and safety are real and demonstrable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10–19&lt;/td&gt;
&lt;td&gt;Real capability, but under-supervised — it works, and that's exactly what makes it risky if left unchecked&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0–9&lt;/td&gt;
&lt;td&gt;Not actually agentic — a well-dressed prompt, regardless of what it's called&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The scoring isn't the point, though — the conversation it forces is. Whoever is presenting the "agent" — a teammate, a thread, a demo — if they can walk through all five categories with specifics, examples, and a straight answer to "what caught it being wrong," it's almost certainly real. If they get vague, pivot to a different demo, or answer with "the model is really good" instead of a specific mechanism, that's told you everything you need to know without saying it directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to Actually Ask in the Room
&lt;/h2&gt;

&lt;p&gt;You don't need all five questions to get a useful answer. In practice, two do most of the work:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"What's this cost per run, and who's watching that number?"&lt;/strong&gt; If nobody can answer immediately, there's no real observability, and that alone should worry you more than any technical detail about the model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"What caught the last time it was wrong?"&lt;/strong&gt; This is the single best filter in the whole framework. A real feedback loop has a specific, mechanical answer — a test that failed, an eval that flagged it, a human checkpoint that caught it. A hand-wave here means the system has never actually been checked, which is a very different claim than "it works."&lt;/p&gt;

&lt;p&gt;If you only remember one line from this, make it the one that started it: cool deck — where's the PROOF?&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Building Local AI Agents in Java with Tools4AI and Ollama: An Insurance Claims Use Case</title>
      <dc:creator>vishalmysore</dc:creator>
      <pubDate>Tue, 28 Jul 2026 21:20:10 +0000</pubDate>
      <link>https://dev.to/vishalmysore/building-local-ai-agents-in-java-with-tools4ai-and-ollama-an-insurance-claims-use-case-2m0m</link>
      <guid>https://dev.to/vishalmysore/building-local-ai-agents-in-java-with-tools4ai-and-ollama-an-insurance-claims-use-case-2m0m</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/vishalmysore/Tools4AI" rel="noopener noreferrer"&gt;Tools4AI&lt;/a&gt; is a 100% Java agentic AI framework that turns any annotated Java method into an AI-callable action. &lt;a href="https://ollama.com" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt; runs open models like Llama 3.1 and Phi-4 locally and exposes an OpenAI-compatible API. Point Tools4AI at &lt;code&gt;http://localhost:11434/v1&lt;/code&gt; and you get a &lt;strong&gt;fully offline, on-premise AI agent&lt;/strong&gt; — no data ever leaves your network. In this tutorial we build an &lt;strong&gt;insurance claims triage agent&lt;/strong&gt; that reads a claimant's free-text incident report, routes it to the right business action, extracts structured data, gates high-value payouts behind a human approval, and records a compliance audit trail.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Who is this for?&lt;/strong&gt; Java developers, solution architects, and engineering leaders in regulated industries (insurance, banking, healthcare) who want agentic AI &lt;strong&gt;without sending sensitive data to a third-party API&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Why local AI agents matter for insurance
&lt;/h2&gt;

&lt;p&gt;Insurance runs on &lt;strong&gt;personally identifiable information (PII)&lt;/strong&gt;: names, addresses, policy numbers, medical details, vehicle data, and loss descriptions. Sending that data to a hosted LLM API creates regulatory, contractual, and reputational risk. At the same time, claims teams are drowning in unstructured text — &lt;strong&gt;First Notice of Loss (FNOL)&lt;/strong&gt; reports, adjuster notes, emails, and call transcripts.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;local AI agent&lt;/strong&gt; solves both problems at once:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Data never leaves your premises.&lt;/strong&gt; The model runs on your own hardware via Ollama.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deterministic business logic stays in Java.&lt;/strong&gt; The LLM decides &lt;em&gt;what&lt;/em&gt; to do; your audited, tested Java code decides &lt;em&gt;how&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Human-in-the-loop and audit trails&lt;/strong&gt; are first-class, so you can satisfy compliance reviewers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That combination — private inference plus governed execution — is exactly what Tools4AI + Ollama gives you.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Tools4AI?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/vishalmysore/Tools4AI" rel="noopener noreferrer"&gt;Tools4AI&lt;/a&gt; (&lt;code&gt;io.github.vishalmysore:tools4ai&lt;/code&gt; on Maven Central) is a lightweight, pure-Java &lt;strong&gt;agentic AI framework&lt;/strong&gt; and ADK. Its core idea is simple and powerful:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Annotate a Java class with &lt;code&gt;@Agent&lt;/code&gt; and its methods with &lt;code&gt;@Action&lt;/code&gt;. Tools4AI scans the classpath, and at runtime it maps a &lt;strong&gt;natural-language prompt&lt;/strong&gt; to the correct method, extracts the parameters, and invokes it — no manual function schemas required.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Key capabilities used in this article:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Action routing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Maps a prompt to the right &lt;code&gt;@Action&lt;/code&gt; method automatically&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Automatic parameter mapping&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Fills method arguments (including POJOs, lists, maps, dates) from the prompt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;POJO transformation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Converts free text into a populated Java object&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Risk gating&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Blocks &lt;code&gt;HIGH&lt;/code&gt;-risk actions unless a human approves&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Audit trail&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Records every action for compliance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Agent memory&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Keeps conversation context across turns&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Because it is provider-agnostic, the same code runs on Gemini, OpenAI, Anthropic — or, as we will do here, a &lt;strong&gt;local Ollama model&lt;/strong&gt; through its OpenAI-compatible endpoint.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Ollama + Java is a great fit for regulated data
&lt;/h2&gt;

&lt;p&gt;Ollama serves open-weight models (Llama 3.1, Phi-4, Mistral, Gemma, and more) behind an &lt;strong&gt;OpenAI-compatible REST API&lt;/strong&gt; at &lt;code&gt;http://localhost:11434/v1&lt;/code&gt;. Because Tools4AI's &lt;code&gt;OpenAiActionProcessor&lt;/code&gt; already speaks that protocol, wiring the two together is pure configuration — &lt;strong&gt;no adapter, no new code&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;Zero data egress&lt;/strong&gt; — inference is local.&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;No API keys, no per-token billing.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Runs in air-gapped / VPC environments.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Same Tools4AI code&lt;/strong&gt; works with a cloud provider later if you choose.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Java 8+&lt;/strong&gt; (Tools4AI compiles at Java 8 bytecode) and Maven&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ollama&lt;/strong&gt; installed — &lt;a href="https://ollama.com/download" rel="noopener noreferrer"&gt;download here&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;~8 GB RAM free for a capable instruction model (more is better)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1 — Install and run Ollama
&lt;/h2&gt;

&lt;p&gt;Pull a model that is good at &lt;strong&gt;function calling&lt;/strong&gt; and start it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama pull llama3.1
ollama run llama3.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ollama now serves the OpenAI-compatible API locally. Verify it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://localhost:11434/v1/chat/completions &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"model":"llama3.1","messages":[{"role":"user","content":"Say OK"}],"stream":false}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should get a JSON response with &lt;code&gt;"content": "OK"&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2 — Add the Tools4AI dependency
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;io.github.vishalmysore&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;tools4ai&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;1.2.1&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Check &lt;a href="https://central.sonatype.com/artifact/io.github.vishalmysore/tools4ai" rel="noopener noreferrer"&gt;Maven Central&lt;/a&gt; for the latest version. If you build from source, remember to enable the &lt;code&gt;-parameters&lt;/code&gt; compiler flag so Tools4AI can read method parameter names.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Step 3 — Point Tools4AI at Ollama
&lt;/h2&gt;

&lt;p&gt;Create &lt;code&gt;tools4ai.properties&lt;/code&gt; on your classpath (e.g. &lt;code&gt;src/main/resources/tools4ai.properties&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="c"&gt;## Any non-empty value works — Ollama ignores the key,
## but Tools4AI only builds the model when a key is present.
&lt;/span&gt;&lt;span class="py"&gt;openAiKey&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;ollama&lt;/span&gt;

&lt;span class="c"&gt;## Ollama's OpenAI-compatible base URL
&lt;/span&gt;&lt;span class="py"&gt;openAiBaseURL&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;http://localhost:11434/v1&lt;/span&gt;

&lt;span class="c"&gt;## Use the exact Ollama model tag
&lt;/span&gt;&lt;span class="py"&gt;openAiModelName&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;llama3.1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two gotchas worth knowing:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;LocalAIActionProcessor&lt;/code&gt; is a stub&lt;/strong&gt; — its methods return &lt;code&gt;null&lt;/code&gt;. Do &lt;strong&gt;not&lt;/strong&gt; use it. The OpenAI-compatible route above is the working path.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Properties file wins over &lt;code&gt;-D&lt;/code&gt; VM options.&lt;/strong&gt; Tools4AI reads &lt;code&gt;tools4ai.properties&lt;/code&gt; first and only falls back to &lt;code&gt;-DopenAiModelName=...&lt;/code&gt; if the file value is empty. If you rely on VM options, leave the corresponding property blank.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Step 4 — Your first local AI call
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.t4a.processor.OpenAiActionProcessor&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Hello&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;OpenAiActionProcessor&lt;/span&gt; &lt;span class="n"&gt;processor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;OpenAiActionProcessor&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;reply&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;processor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Reply with exactly one word: PONG"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reply&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// -&amp;gt; PONG&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That single &lt;code&gt;query()&lt;/code&gt; call already round-trips through Tools4AI's config loader, langchain4j, and your local Ollama model. If it prints &lt;code&gt;PONG&lt;/code&gt;, you are fully offline and ready to build an agent.&lt;/p&gt;




&lt;h2&gt;
  
  
  The insurance use case: an FNOL claims triage agent
&lt;/h2&gt;

&lt;p&gt;Let's build something real: an agent that handles the &lt;strong&gt;First Notice of Loss (FNOL)&lt;/strong&gt; — the moment a policyholder reports an incident. Our agent will:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Route&lt;/strong&gt; a claimant's message to the right business action (check policy, file a claim, estimate a payout, or escalate).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extract&lt;/strong&gt; a structured claim record from a free-text incident description.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gate&lt;/strong&gt; large payouts behind a mandatory human approval.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audit&lt;/strong&gt; every action for compliance.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  1. Define your business actions as agents
&lt;/h3&gt;

&lt;p&gt;This is the whole point of Tools4AI: your business logic is just an ordinary Java class. Annotate the class with &lt;code&gt;@Agent&lt;/code&gt;, annotate each callable method with &lt;code&gt;@Action&lt;/code&gt;, and you're done — &lt;strong&gt;no interface to implement, no boilerplate&lt;/strong&gt;. Tools4AI scans the classpath, registers &lt;em&gt;every&lt;/em&gt; &lt;code&gt;@Action&lt;/code&gt; method (many per class is fine), instantiates the class for you, and reads &lt;code&gt;riskLevel&lt;/code&gt; straight from the annotation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.t4a.annotations.Action&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.t4a.annotations.Agent&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.t4a.api.ActionRisk&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;@Agent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;groupName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"claims"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;groupDescription&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"insurance policy and claims actions"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ClaimsAgent&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Action&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"check whether an insurance policy is active and in good standing "&lt;/span&gt;
            &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"given its policy number"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;checkPolicyStatus&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;policyNumber&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"Policy "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;policyNumber&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;" is ACTIVE | coverage: AUTO | deductible: $500"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Action&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"open a new insurance claim for a policyholder describing an incident, "&lt;/span&gt;
            &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"given the policy number, the incident type (collision, theft, fire, water damage) "&lt;/span&gt;
            &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"and a short description"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;fileClaim&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;policyNumber&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;incidentType&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;claimId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"CLM-"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nc"&gt;Math&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;abs&lt;/span&gt;&lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="n"&gt;policyNumber&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;incidentType&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;hashCode&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;100000&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"Opened claim "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;claimId&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;" ("&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;incidentType&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;") for policy "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;policyNumber&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Action&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"estimate the payout amount in US dollars for a described incident, "&lt;/span&gt;
            &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"given the incident type and the estimated damage amount in dollars"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;estimatePayout&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;incidentType&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;estimatedDamage&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;payout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Math&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;max&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;estimatedDamage&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// minus deductible&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"Estimated payout for "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;incidentType&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;": $"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;payout&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Settling a claim moves money. Declaring riskLevel = HIGH right on the annotation is enough:&lt;/span&gt;
    &lt;span class="c1"&gt;// Tools4AI refuses to trigger it via auto-prediction, so it can only be invoked explicitly.&lt;/span&gt;
    &lt;span class="nd"&gt;@Action&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"approve and settle a claim payout to the policyholder, "&lt;/span&gt;
            &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"given the claim id, the amount in dollars, and who approved it"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;riskLevel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ActionRisk&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;HIGH&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;settleClaim&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;claimId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;approvedBy&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"SETTLED "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;claimId&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;" for $"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;" approved by "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;approvedBy&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Two things to know.&lt;/strong&gt; (1) &lt;code&gt;riskLevel&lt;/code&gt; belongs on &lt;code&gt;@Action&lt;/code&gt;, not &lt;code&gt;@Agent&lt;/code&gt; (&lt;code&gt;@Agent&lt;/code&gt; only carries &lt;code&gt;groupName&lt;/code&gt;, &lt;code&gt;groupDescription&lt;/code&gt;, and an optional &lt;code&gt;prompt&lt;/code&gt;). (2) The class needs a public no-arg constructor — Tools4AI instantiates it for you.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(There is an older style where an action class &lt;code&gt;implements JavaMethodAction&lt;/code&gt;. Avoid it unless you need it: that path registers only the first &lt;code&gt;@Action&lt;/code&gt; method per class and ignores &lt;code&gt;@Action(riskLevel=…)&lt;/code&gt; — you'd have to split every action into its own class and override &lt;code&gt;getActionRisk()&lt;/code&gt;. Plain &lt;code&gt;@Agent&lt;/code&gt; classes, as above, have neither limitation.)&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;
&lt;span class="err"&gt;###&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="nc"&gt;Natural&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;language&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="n"&gt;routing&lt;/span&gt;

&lt;span class="nc"&gt;Now&lt;/span&gt; &lt;span class="n"&gt;let&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="n"&gt;decide&lt;/span&gt; &lt;span class="n"&gt;which&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt; &lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;plain&lt;/span&gt; &lt;span class="nc"&gt;English&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="nc"&gt;No&lt;/span&gt; &lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;no&lt;/span&gt; &lt;span class="n"&gt;intent&lt;/span&gt; &lt;span class="n"&gt;parser&lt;/span&gt; &lt;span class="err"&gt;—&lt;/span&gt; &lt;span class="nc"&gt;Tools4AI&lt;/span&gt; &lt;span class="n"&gt;does&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;mapping&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
java&lt;br&gt;
import com.t4a.processor.OpenAiActionProcessor;&lt;/p&gt;

&lt;p&gt;OpenAiActionProcessor agent = new OpenAiActionProcessor();&lt;/p&gt;

&lt;p&gt;// The AI picks checkPolicyStatus and extracts policyNumber = "AUTO-88213"&lt;br&gt;
Object status = agent.processSingleAction(&lt;br&gt;
    "Can you tell me if my policy AUTO-88213 is still active?");&lt;br&gt;
System.out.println(status);&lt;br&gt;
// -&amp;gt; Policy AUTO-88213 is ACTIVE, auto coverage, $500 deductible&lt;/p&gt;

&lt;p&gt;// The AI picks fileClaim and extracts all three arguments&lt;br&gt;
Object claim = agent.processSingleAction(&lt;br&gt;
    "I need to file a claim on policy AUTO-88213. Someone rear-ended my car " +&lt;br&gt;
    "in a parking lot and the bumper is cracked.");&lt;br&gt;
System.out.println(claim);&lt;br&gt;
// -&amp;gt; Opened claim CLM-12345 (collision) for policy AUTO-88213&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
You can also pass the action explicitly (`agent.processSingleAction(prompt, new FileClaimAction())`) when you already know which capability to use — handy for deterministic, single-purpose endpoints.

### 3. Turn free text into a structured claim (POJO extraction)

Claimants describe incidents in messy prose. Use `OpenAIPromptTransformer` to convert that into a clean Java object you can validate and persist. The `@Prompt` annotation adds per-field instructions such as date formatting.

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
java&lt;br&gt;
import com.t4a.annotations.Prompt;&lt;br&gt;
import java.util.Date;&lt;/p&gt;

&lt;p&gt;public class ClaimReport {&lt;br&gt;
    public String  claimantName;&lt;br&gt;
    public String  policyNumber;&lt;br&gt;
    public String  incidentType;   // e.g. collision, theft, fire, water damage&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Prompt(describe = "estimated cost of damage in US dollars, number only")
public double  estimatedDamage;

@Prompt(dateFormat = "yyyy-MM-dd", describe = "date the incident occurred")
public Date    incidentDate;

public String  location;

public String toString() {
    return "ClaimReport{name=" + claimantName + ", policy=" + policyNumber +
           ", type=" + incidentType + ", damage=$" + estimatedDamage +
           ", date=" + incidentDate + ", location=" + location + "}";
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;}&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
java&lt;br&gt;
import com.t4a.transform.OpenAIPromptTransformer;&lt;/p&gt;

&lt;p&gt;OpenAIPromptTransformer transformer = new OpenAIPromptTransformer();&lt;/p&gt;

&lt;p&gt;String fnol =&lt;br&gt;
    "Hi, this is Priya Sharma, policy HOME-55021. On July 12th 2026 a burst pipe " +&lt;br&gt;
    "flooded my kitchen in Austin. A plumber estimated about $3,200 in damage.";&lt;/p&gt;

&lt;p&gt;ClaimReport report = (ClaimReport) transformer.transformIntoPojo(fnol, ClaimReport.class.getName());&lt;br&gt;
System.out.println(report);&lt;br&gt;
// -&amp;gt; ClaimReport{name=Priya Sharma, policy=HOME-55021, type=water damage,&lt;br&gt;
//               damage=$3200.0, date=2026-07-12, location=Austin}&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
One call turns an unstructured report into a validated, typed record ready for your claims pipeline.

### 4. Gate high-value payouts with human-in-the-loop

Settling a claim moves money — it must **never** be triggered automatically by the model. With the **core** Tools4AI API you get two guarantees, no extra libraries required:

1. **Auto-prediction refuses `HIGH`-risk actions.** If you call `processSingleAction(prompt)` with no explicit action and the best match is `HIGH` risk, it is not executed.
2. **Explicit calls run only if a human approves.** Pass a `HumanInLoop` approver to `processSingleAction(prompt, action, approver, explain)` — the action runs only when the approver returns valid.

Provide an approver by implementing `HumanInLoop`. In production each call would open a ticket, page an adjuster, or invoke your workflow engine and block until a decision returns:

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
java&lt;br&gt;
import com.t4a.detect.FeedbackLoop;&lt;br&gt;
import com.t4a.detect.HumanInLoop;&lt;br&gt;
import java.util.Map;&lt;/p&gt;

&lt;p&gt;public class AdjusterApproval implements HumanInLoop {&lt;br&gt;
    private final boolean approve;                 // wire to a real approval channel&lt;br&gt;
    public AdjusterApproval(boolean approve) { this.approve = approve; }&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Override
public FeedbackLoop allow(String prompt, String methodName, Map&amp;lt;String, Object&amp;gt; params) {
    return () -&amp;gt; approve;
}
@Override
public FeedbackLoop allow(String prompt, String methodName, String params) {
    return () -&amp;gt; approve;   // core calls this String overload with the action JSON
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;}&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
Because `ClaimsAgent` is a plain `@Agent` POJO (not an `AIAction`), grab the registered action from Tools4AI's registry by name to invoke it explicitly:

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
java&lt;br&gt;
import com.t4a.api.AIAction;&lt;br&gt;
import com.t4a.predict.PredictionLoader;&lt;br&gt;
import com.t4a.processor.LogginggExplainDecision;&lt;br&gt;
import com.t4a.processor.OpenAiActionProcessor;&lt;/p&gt;

&lt;p&gt;OpenAiActionProcessor agent = new OpenAiActionProcessor();&lt;br&gt;
AIAction settle = PredictionLoader.getInstance().getAiAction("settleClaim");&lt;/p&gt;

&lt;p&gt;// Human declines -&amp;gt; settleClaim never runs; you get "Human verification failed"&lt;br&gt;
agent.processSingleAction("Settle claim CLM-12345 for $8000, approved by Vishal",&lt;br&gt;
        settle, new AdjusterApproval(false), new LogginggExplainDecision());&lt;/p&gt;

&lt;p&gt;// Human approves -&amp;gt; the settlement executes&lt;br&gt;
Object result = agent.processSingleAction("Settle claim CLM-12345 for $8000, approved by Vishal",&lt;br&gt;
        settle, new AdjusterApproval(true), new LogginggExplainDecision());&lt;br&gt;
// -&amp;gt; SETTLED CLM-12345 for $8000.0 approved by Vishal&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
&amp;gt; Because `riskLevel = HIGH` is declared on the `@Action`, the auto-prediction refusal in step 1 works with no extra code.

### 5. Going further: the agent toolkit

Beyond the core, Tools4AI ships an **agent toolkit** (`com.t4a.agent.*`) of composable decorators over the `AIProcessor` interface — a two-signoff `RiskGatedActionProcessor`, a JSON `AuditedActionProcessor` for compliance, `InMemoryActionMetrics`, retry/rate-limit resilience, `AgentMemory` for multi-turn claims, and multi-agent orchestration. For example, a compliance audit trail is one wrapper:

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
java&lt;br&gt;
// Requires a Tools4AI build that includes the agent toolkit (com.t4a.agent.*)&lt;br&gt;
AuditTrail trail = new JsonFileAuditTrail("/var/claims/audit.jsonl");&lt;br&gt;
AIProcessor audited = new AuditedActionProcessor(new OpenAiActionProcessor(), trail);&lt;br&gt;
audited.processSingleAction("File a claim on policy AUTO-88213 for a cracked bumper");&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
&amp;gt; **Availability note:** the agent toolkit is part of newer Tools4AI builds and may not be in every published Maven Central release. Check that `com.t4a.agent.*` is present in your resolved jar before importing it. The InsureJAI demo above uses **core only**, so it builds against the published artifact as-is. For multi-turn claims, `AgentMemory` / `PersistentFileAgentMemory` from the same toolkit keep conversation context across turns and restarts.

---

## Choosing the right local model

**Model capability directly determines how well function calling works.** In our testing:

- **Capable instruction models** (`llama3.1`, `phi4`) reliably route prompts to the correct `@Action` **and** fill in the arguments — including numbers, dates, and nested POJOs.
- **Very small models** (e.g. `gemma3:270m`) often select the right action but leave parameters empty. They are fine for a plain `query()`, but unreliable for full tool calling.

**Recommendation:** start with `llama3.1` (8B) or `phi4` (14B) for claims-style extraction. Use a larger model if your prompts are long or multi-entity. Match the model to your hardware — bigger models need more RAM/VRAM and are slower on CPU.

## Production hardening: composing the agent stack

Every agent-toolkit capability is a decorator over the `AIProcessor` interface, so they nest in any order — and the order encodes your policy. A production-grade claims agent might look like this (requires the `com.t4a.agent.*` toolkit noted above):

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
java&lt;br&gt;
AIProcessor claimsAgent =&lt;br&gt;
    new AuditedActionProcessor(                 // records the final outcome&lt;br&gt;
        new MeteredActionProcessor(             // latency + error rates&lt;br&gt;
            new RiskGatedActionProcessor(       // human approval for HIGH-risk settlements&lt;br&gt;
                new RetryActionProcessor(       // survive transient model hiccups&lt;br&gt;
                    new OpenAiActionProcessor(), // -&amp;gt; your local Ollama model&lt;br&gt;
                    3, 500, null),&lt;br&gt;
                new AdjusterApproval()),&lt;br&gt;
            metrics),&lt;br&gt;
        new JsonFileAuditTrail("/var/claims/audit.jsonl"));&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Reading outside-in: audit the *final* decision, measure *user-visible* latency, require approval *once* (not per retry), and retry transient failures closest to the model. Rearranging the layers changes the semantics — choose deliberately.

## Frequently asked questions

**Does any claim data leave my network?**
No. With Ollama the model runs locally and Tools4AI talks to `http://localhost:11434`. Nothing is sent to a hosted API.

**Do I need an OpenAI API key?**
No. Set `openAiKey=ollama` (any non-empty placeholder). Ollama ignores it; Tools4AI just needs a non-empty value to initialize the client.

**Why does the model pick the right action but leave the fields blank?**
The model is too small for reliable function calling. Switch to `llama3.1` or `phi4`.

**Can I use the same code with a cloud provider later?**
Yes. Tools4AI is provider-agnostic. Swap the processor (or the base URL/model) and your `@Agent` / `@Action` code is unchanged.

**Is this production-ready?**
The building blocks — risk gating, audit, retry, metrics, memory — are designed for production. As always, validate model outputs, keep humans in the loop for money-moving actions, and test against your own data.

## Conclusion

With **Tools4AI + Ollama** you get the best of both worlds: **private, on-premise LLM inference** and **governed, testable Java business logic**. In a few dozen lines we built an insurance FNOL agent that understands natural language, extracts structured claims, protects high-value payouts behind human approval, and logs everything for compliance — all without a single byte of PII leaving the building.

- 🏥 Runnable project for this article: [github.com/vishalmysore/insureJAI](https://github.com/vishalmysore/insureJAI)
- ⭐ Star the framework: [github.com/vishalmysore/Tools4AI](https://github.com/vishalmysore/Tools4AI)
- 📦 Maven Central: [io.github.vishalmysore:tools4ai](https://central.sonatype.com/artifact/io.github.vishalmysore/tools4ai)
- 📚 Architecture deep-dive: [Tools4AI ARCHITECTURE.md](https://github.com/vishalmysore/Tools4AI/blob/main/ARCHITECTURE.md)

*Build private AI agents in Java. Keep your data where it belongs.*
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>ai</category>
      <category>java</category>
      <category>llm</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>The Real Reason 40% of Agentic AI Projects Are Getting Cancelled (It's Not the Model)</title>
      <dc:creator>vishalmysore</dc:creator>
      <pubDate>Wed, 22 Jul 2026 23:26:30 +0000</pubDate>
      <link>https://dev.to/vishalmysore/the-real-reason-40-of-agentic-ai-projects-are-getting-cancelled-its-not-the-model-45mb</link>
      <guid>https://dev.to/vishalmysore/the-real-reason-40-of-agentic-ai-projects-are-getting-cancelled-its-not-the-model-45mb</guid>
      <description>&lt;p&gt;&lt;em&gt;Gartner says nearly half of agentic AI initiatives will be scrapped before 2027. I've spent the last year building the four pieces of infrastructure that pilots skip — and production punishes.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;Every few weeks there's a new model that's supposed to change everything. Grok 4.5 lands with a viral benchmark fight. GPT-5.6 ships days later. Claude gets a new tier. Somewhere a Gemini release slips again. Each launch comes with the same implicit promise: &lt;em&gt;this one is finally smart enough to run your agents in production.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;And yet Gartner now expects &lt;strong&gt;&lt;a href="https://www.gartner.com/en/newsroom/press-releases/2025-06-25-gartner-predicts-over-40-percent-of-agentic-ai-projects-will-be-canceled-by-end-of-2027" rel="noopener noreferrer"&gt;over 40% of agentic AI projects to be cancelled before 2027&lt;/a&gt;&lt;/strong&gt; — not for lack of a capable model, but because of escalating costs, unclear business value, and inadequate risk controls. IBM's numbers tell the same story from a different angle: in a Q1 2025 survey of 2,000 CEOs, &lt;strong&gt;&lt;a href="https://www.aol.com/ceos-just-fraction-ai-initiatives-173518171.html" rel="noopener noreferrer"&gt;only 25% of AI initiatives had delivered the ROI they were expected to&lt;/a&gt;&lt;/strong&gt;. McKinsey's own research shows the same shape of gap — &lt;strong&gt;&lt;a href="https://www.mckinsey.com/featured-insights/week-in-charts/ai-at-work-but-not-at-scale" rel="noopener noreferrer"&gt;88% of organizations now use AI in at least one business function, but only 7% say it's fully scaled&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I don't think that gap is a model problem. I've built four separate systems this year, each one forced on me by a different way agents fail in the real world, and not one of those failures had anything to do with which LLM was behind the curtain. It was always a missing layer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pilots only need one layer to hold. Production needs all four.
&lt;/h2&gt;

&lt;p&gt;A demo succeeds with a clean prompt, a happy path, and an audience that doesn't push back. Production has none of that mercy. Here are the four layers I keep re-discovering the hard way — and the projects I built once I got tired of discovering them.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. The Spec Layer — why the agent is "almost right"
&lt;/h3&gt;

&lt;p&gt;The first failure mode isn't a bad answer. It's a &lt;em&gt;plausible&lt;/em&gt; one. AI coding assistants and agents don't leave gaps empty — they fill them with confident guesses that compile, pass the demo, and quietly do the wrong thing once real data hits them.&lt;/p&gt;

&lt;p&gt;That's the problem I built &lt;strong&gt;ZeeSpec&lt;/strong&gt; to solve: a structured specification framework built on the Zachman Framework's dimensions and the simplicity of 5W1H — What, Where, Who, When, Why, How. The rule is blunt: if you can't answer a question about your system, that system is undefined — and undefined space is space the AI will fill on your behalf. Sixty questions, about an hour, and the agent stops inventing because there's nothing left to invent.&lt;/p&gt;

&lt;p&gt;→ &lt;a href="https://vishalmysore.github.io/zeespec/" rel="noopener noreferrer"&gt;Live framework&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2. The Guardrail Layer — why "it worked in staging" doesn't mean it's safe
&lt;/h3&gt;

&lt;p&gt;Even a well-specified agent will eventually be handed a request it shouldn't fully honor. This is the layer most pilots never test, because pilots don't have real stakes.&lt;/p&gt;

&lt;p&gt;I built &lt;strong&gt;Harness Engineering&lt;/strong&gt; after realizing that the part of an agent stack that actually matters isn't the model call — it's everything wrapped around it. The project routes calls across OpenAI, Anthropic, Gemini, and NVIDIA NIM models interchangeably, but the part I actually demo is the guardrail layer: a hepatotoxicity check that blocks a dangerous drug interaction, a fraud rule that routes a suspicious claim straight to a SIU referral, a penicillin-allergy check that stops a bad recommendation before it reaches anyone. It even ships a Mock AI provider that runs the full tool-calling and guardrail sequence with zero network calls — so every block you see in a demo is real logic, not a scripted illusion.&lt;/p&gt;

&lt;p&gt;Swap in a smarter model and none of that logic appears on its own. It has to be engineered in.&lt;/p&gt;

&lt;p&gt;→ &lt;a href="https://github.com/vishalmysore/harnessEngineeringDemo" rel="noopener noreferrer"&gt;Source&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3. The Memory Layer — why the agent keeps making the same mistake
&lt;/h3&gt;

&lt;p&gt;LLMs are stateless. Every new session is total amnesia — the agent doesn't remember the itinerary it planned yesterday, or the mistake it made suggesting a hotel an hour from the airport. Most teams try to paper over this with RAG, which is good at storing documents but was never designed to store &lt;em&gt;experience&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;That's what &lt;strong&gt;ReasoningBank&lt;/strong&gt; is for — an AI travel agent I built as an independent demonstration of the ReasoningBank memory pattern, which structures and persists an agent's actual experience across sessions instead of just its source documents. Instead of re-learning the same failure every single run, the agent's mistakes become part of what it checks against next time.&lt;/p&gt;

&lt;p&gt;This is the layer that quietly wrecks ROI math: if your agent re-derives the same lesson on every session, you're paying full inference cost to relearn things you've already paid to learn once.&lt;/p&gt;

&lt;p&gt;→ &lt;a href="https://vishalmysore.github.io/reasoningBankDemo" rel="noopener noreferrer"&gt;Live demo&lt;/a&gt; · &lt;a href="https://github.com/vishalmysore/reasoningBankDemo" rel="noopener noreferrer"&gt;Source&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  4. The Coordination Layer — why the failure isn't in any single agent
&lt;/h3&gt;

&lt;p&gt;The last failure mode only shows up once you have more than one agent, and by 2026 almost nobody has just one. The bugs stop living inside an agent and start living in the seams &lt;em&gt;between&lt;/em&gt; agents — who's allowed to call whom, which protocol carries which kind of information, what happens when two agents disagree.&lt;/p&gt;

&lt;p&gt;I've spent a chunk of this year building out an &lt;strong&gt;Agentic Mesh&lt;/strong&gt; — a network of specialized agents (in one build, four separate servers handling completely different domains) coordinated through the A2A and MCP protocols running side by side rather than picking one. The point isn't that multi-agent systems are hard to build; it's that they're easy to build badly, because most teams treat the protocol layer as an afterthought instead of the thing that determines whether the mesh survives contact with a real workload.&lt;/p&gt;

&lt;p&gt;→ &lt;a href="https://vishalmysore-a2apw.hf.space/supplyChain" rel="noopener noreferrer"&gt;Live pattern&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this explains the 40%
&lt;/h2&gt;

&lt;p&gt;Line up those four layers against Gartner's number and the pattern gets obvious. A demo only has to clear one of them — usually the spec, because that's the one you control by writing a good prompt. Production has to clear all four, simultaneously, under load, with real users who don't behave like a demo script. Most agentic AI projects don't get cancelled because the model got dumber between the pilot and the rollout. They get cancelled because the team never built the other three layers, and the model was never going to build them either.&lt;/p&gt;

&lt;p&gt;That's also why the next model release won't move the 40% number much. None of these four layers live inside the weights. They live in the specification you wrote before the first prompt, the guardrails you wrote around the tool calls, the memory store you built to survive the session boundary, and the protocol you chose to let agents actually talk to each other. Swap GPT-5.6 for Grok 4.5 for whatever ships next month, and an agent with none of those four layers will fail in production exactly as fast as it did before.&lt;/p&gt;

&lt;p&gt;If you're scoping an agentic AI project right now, the honest audit isn't "which model should we use." It's: can you answer every question about what this system is (spec), what it's allowed to do when it's wrong (guardrails), what it remembers between sessions (memory), and how it talks to the other agents it will inevitably need (coordination)? Wherever that audit comes back empty is exactly where your project will show up in next year's cancellation statistics.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Disclaimer: the stats below are cited from Gartner, IBM, and McKinsey publications, linked at each mention. Everything else — the four-layer framework, the diagnosis of why projects fail, the connection I'm drawing between the two — is my own opinion, based on my own projects, not a finding from any of those firms.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I write about agentic AI architecture, spec-driven development, and the infrastructure layer nobody puts in the keynote. Links to the projects above, and the rest of my work, are on &lt;a href="https://github.com/vishalmysore" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; and &lt;a href="https://medium.com/@visrow" rel="noopener noreferrer"&gt;Medium&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Serverless AI in a Browser Tab: Java WebAssembly + Local WebGPU LLMs</title>
      <dc:creator>vishalmysore</dc:creator>
      <pubDate>Tue, 30 Jun 2026 22:14:03 +0000</pubDate>
      <link>https://dev.to/vishalmysore/serverless-ai-in-a-browser-tab-java-webassembly-local-webgpu-llms-f4g</link>
      <guid>https://dev.to/vishalmysore/serverless-ai-in-a-browser-tab-java-webassembly-local-webgpu-llms-f4g</guid>
      <description>&lt;h3&gt;
  
  
  A deep technical whitepaper on building a zero-infrastructure RAG architecture where the business logic is Java compiled to WebAssembly and the intelligence is a quantized LLM running on your own GPU
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Reference implementation:&lt;/strong&gt; &lt;a href="https://github.com/vishalmysore/javaWASM" rel="noopener noreferrer"&gt;github.com/vishalmysore/javaWASM&lt;/a&gt; · &lt;strong&gt;Live demo:&lt;/strong&gt; &lt;a href="https://vishalmysore.github.io/javaWASM/" rel="noopener noreferrer"&gt;vishalmysore.github.io/javaWASM&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Abstract
&lt;/h2&gt;

&lt;p&gt;For a decade the default shape of an "AI application" has been fixed: a thin client in the browser, a fat backend on someone else's servers, and a metered API call to a model hosted in a data center you will never see. This paper describes an architecture that inverts that shape completely. The entire system — document parsing, text chunking, vector storage, similarity search, context compression, multi-agent orchestration, &lt;em&gt;and&lt;/em&gt; large-language-model inference — runs inside a single browser tab, on the user's own hardware, with &lt;strong&gt;no backend, no database service, and no API key&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Two technologies make this possible. &lt;strong&gt;WebAssembly (Wasm)&lt;/strong&gt; lets us compile a real, statically-typed business-logic core written in &lt;strong&gt;Java&lt;/strong&gt; down to a compact, near-native binary that runs in the browser sandbox. &lt;strong&gt;WebGPU&lt;/strong&gt; gives that same tab direct access to the machine's GPU, so a quantized small language model can generate tokens locally. We show how to weld these together with on-device embeddings and an in-browser vector database to produce a Retrieval-Augmented Generation (RAG) pipeline that is private by construction and costs nothing to operate.&lt;/p&gt;

&lt;p&gt;This is not a thought experiment. Every claim here is backed by a deployed, open-source reference implementation, and we are deliberately honest about the sharp edges we hit along the way.&lt;/p&gt;




&lt;h2&gt;
  
  
  Project Disclaimer &amp;amp; Intent
&lt;/h2&gt;

&lt;p&gt;My intention behind exploring this architecture is to push the boundaries of what is possible and challenge the traditional AI application model.&lt;/p&gt;

&lt;p&gt;The question I wanted to explore is: &lt;strong&gt;can we move more intelligence and computation closer to the user?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Can edge computing evolve beyond servers? Can technologies like WebAssembly, WebGPU, WebRTC, and local AI models create a new era of applications where privacy, performance, and cost are balanced differently?&lt;/p&gt;

&lt;p&gt;This project is not about replacing cloud AI. Large-scale models and centralized infrastructure will continue to play a critical role. Instead, it is an exploration of what becomes possible when parts of the AI stack move from the cloud into the browser.&lt;/p&gt;

&lt;p&gt;The goal is to experiment with a future where applications can be more private, more resilient, and less dependent on backend infrastructure — while being honest about the current trade-offs around model size, hardware limitations, and browser maturity.&lt;/p&gt;

&lt;p&gt;The future may not be cloud versus edge. It may be a smarter balance between both.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. The problem with the current architecture
&lt;/h2&gt;

&lt;p&gt;The conventional AI stack has four structural taxes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Cost.&lt;/strong&gt; Every inference is a billable event. A feature that "summarizes the user's notes" has a unit economics problem the moment it has users.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Privacy.&lt;/strong&gt; To get an answer, the user's data must leave their device and transit a third party. For legal documents, medical notes, source code, or personal journals, that is often a non-starter.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Latency &amp;amp; availability.&lt;/strong&gt; A network round-trip sits on the critical path of every interaction, and your app is only as available as the upstream API.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Operational drag.&lt;/strong&gt; Servers, autoscaling, key rotation, rate-limit handling, vector-DB clusters — infrastructure that must be built, secured, paid for, and kept alive.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The interesting observation in 2026 is that &lt;strong&gt;none of these taxes are fundamental&lt;/strong&gt; any more. Browsers have quietly become capable of running compute-heavy code at near-native speed (Wasm) and of driving the GPU directly (WebGPU). Embedding models have shrunk to tens of megabytes; instruction-tuned LLMs now ship in sub-gigabyte quantized form. The pieces to move the &lt;em&gt;entire&lt;/em&gt; stack into the client exist — they just have not been assembled into a coherent architecture. That assembly is the subject of this paper.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. What is WebAssembly?
&lt;/h2&gt;

&lt;p&gt;WebAssembly is a &lt;strong&gt;portable binary instruction format for a stack-based virtual machine&lt;/strong&gt;. It is not a language you write; it is a compilation &lt;em&gt;target&lt;/em&gt;. C, C++, Rust, Go, Kotlin, and — crucially for us — Java can all be compiled into a &lt;code&gt;.wasm&lt;/code&gt; module that any modern browser (≈96% of global sessions) can load and execute.&lt;/p&gt;

&lt;p&gt;Three properties matter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Near-native performance.&lt;/strong&gt; Wasm is designed to be decoded and JIT/AOT-compiled extremely fast, with a predictable instruction set close to real CPU semantics. For compute-bound work it is typically &lt;strong&gt;1.5×–20× faster than equivalent JavaScript&lt;/strong&gt;, with far less variance from garbage-collection pauses and de-optimization.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A capability-secure sandbox.&lt;/strong&gt; A Wasm module has &lt;em&gt;no&lt;/em&gt; ambient access to the DOM, the network, the filesystem, or memory outside its own linear heap. Everything it can touch must be explicitly imported from the host. This is a security model, not an afterthought — it is why running untrusted compiled code in a browser is safe.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Language independence.&lt;/strong&gt; Wasm breaks the JavaScript monoculture of the web. You can bring a mature, statically-typed, heavily-tested codebase in another language to the front end without a rewrite.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2.1 WasmGC: the part that makes Java practical
&lt;/h3&gt;

&lt;p&gt;Early Wasm had a flat linear memory and no notion of managed objects. A language like Java — built around a garbage-collected object heap — had to &lt;em&gt;ship its own GC and memory manager&lt;/em&gt; compiled into the module. That worked, but it bloated binaries and fought the host.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;WasmGC&lt;/strong&gt; (the WebAssembly Garbage Collection proposal, now broadly shipped) adds first-class managed heap types to the VM itself. Managed languages can emit &lt;code&gt;struct&lt;/code&gt; and &lt;code&gt;array&lt;/code&gt; types that the &lt;strong&gt;browser's own garbage collector&lt;/strong&gt; manages. The payoff is dramatic: a Java program compiles to a &lt;em&gt;lean&lt;/em&gt; module (our entire business core is a few hundred kilobytes) that shares the host GC, interoperates cleanly with JavaScript objects, and starts fast. WasmGC is the enabling technology that turns "Java in the browser" from a curiosity into an engineering choice.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Why Wasm is a game changer
&lt;/h2&gt;

&lt;p&gt;It is tempting to frame Wasm as merely "faster JavaScript." That undersells it. The shift is architectural:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;Before (JS-only)&lt;/th&gt;
&lt;th&gt;With Wasm&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Languages&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;JavaScript / TypeScript&lt;/td&gt;
&lt;td&gt;Any language that compiles to Wasm&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Performance&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;JIT, GC-pause-prone&lt;/td&gt;
&lt;td&gt;Near-native, predictable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Code reuse&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Rewrite backend logic in JS&lt;/td&gt;
&lt;td&gt;Compile existing Java/Rust/C++ as-is&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Trust boundary&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Same-origin scripts&lt;/td&gt;
&lt;td&gt;Capability-secure sandbox by default&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Where it runs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Browser&lt;/td&gt;
&lt;td&gt;Browser, edge, serverless, embedded, plugins&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The deepest consequence is that &lt;strong&gt;Wasm moves the unit of deployment from "a service" to "a portable binary."&lt;/strong&gt; The same compiled core can run in a browser tab, on an edge node, inside a serverless function, or embedded in another application — unchanged. For our purposes, the relevant instance of that generality is simple and radical: &lt;em&gt;the business logic that used to require a server can now ship to, and run on, the client.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Writing the business core in Java
&lt;/h2&gt;

&lt;p&gt;We chose Java for the core because it is exactly the kind of language Wasm was supposed to liberate: statically typed, with a vast standard library, decades of tooling, and an enormous corpus of existing business logic. The compiler is &lt;strong&gt;&lt;a href="https://teavm.org" rel="noopener noreferrer"&gt;TeaVM&lt;/a&gt;&lt;/strong&gt; (v0.15), which takes Java bytecode and emits a WasmGC module.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.1 Separation of concerns
&lt;/h3&gt;

&lt;p&gt;The architecture draws one hard line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌──────────────────────────────────────────────┐
│  JAVA WASM CORE  (the "brain" — TeaVM/WasmGC)  │
│  • document chunking                           │
│  • vector storage + cosine similarity          │
│  • top-K retrieval + context assembly          │
│  • context compression (PCA, k-means, etc.)    │
│  • multi-agent supervisor logic                │
│  • UI construction (DOM + canvas)              │
└──────────────────────────────────────────────┘
                 ▲   │   @JSExport / @JSBody
                 │   ▼
┌──────────────────────────────────────────────┐
│  JS HARDWARE LAYER  (the "muscles")            │
│  • Transformers.js — text embeddings           │
│  • WebLLM — LLM inference on WebGPU             │
│  • sqlite-vec (WASM) — vector KNN engine        │
│  • IndexedDB — durable storage                 │
└──────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Java core owns the &lt;strong&gt;deterministic, algorithmic, business logic&lt;/strong&gt;. The JavaScript layer owns the &lt;strong&gt;asynchronous, hardware-bound, I/O work&lt;/strong&gt; — the things browsers are natively good at. Neither layer reaches into the other's domain; they communicate only across a narrow, explicit boundary.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.2 The interop surface
&lt;/h3&gt;

&lt;p&gt;TeaVM's JavaScript Object (JSO) layer provides two annotations that constitute the entire bridge:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;@JSExport&lt;/code&gt;&lt;/strong&gt; — expose a Java method so JavaScript can call it. After the module loads, exported methods appear on &lt;code&gt;instance.exports&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RAGOrchestrator&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@JSExport&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;buildContext&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;queryText&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;queryCsv&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;compress&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;qv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parseVector&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;queryCsv&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;retrieved&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;searchTopContext&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;qv&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;          &lt;span class="c1"&gt;// Java cosine retrieval&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;compress&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;ContextCompressor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;compress&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;queryText&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;retrieved&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;600&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;retrieved&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;@JSBody&lt;/code&gt;&lt;/strong&gt; — call a JavaScript function from Java by inlining a snippet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NativeAIBridge&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@JSBody&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"systemPrompt"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"userQuery"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"context"&lt;/span&gt;&lt;span class="o"&gt;},&lt;/span&gt;
            &lt;span class="n"&gt;script&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"window.streamSLMInference(systemPrompt, userQuery, context);"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;native&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;executeSLM&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;systemPrompt&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;userQuery&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the JavaScript side, loading the module is three lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;teavm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;TeaVM&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;wasmGC&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;wasm-gc/classes.wasm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;teavm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;([]);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;teavm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;buildContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// calls into Java/Wasm&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4.3 A hard-won lesson: the synchronous boundary
&lt;/h3&gt;

&lt;p&gt;The single most important design constraint is this: &lt;strong&gt;you cannot block on a JavaScript Promise from inside synchronous Wasm code.&lt;/strong&gt; Embedding a sentence (Transformers.js) and generating a token (WebLLM) are inherently asynchronous. If Java calls an &lt;code&gt;async&lt;/code&gt; JS function expecting a return value, it gets a &lt;code&gt;Promise&lt;/code&gt;, not the data.&lt;/p&gt;

&lt;p&gt;The resolution is a clean rule that shaped the whole system: &lt;strong&gt;JavaScript owns the async orchestration; Java owns the synchronous compute.&lt;/strong&gt; When Java needs many embeddings, it does &lt;em&gt;not&lt;/em&gt; pull them. Instead it pushes work outward — it splits text and emits each fragment to JS via &lt;code&gt;@JSBody&lt;/code&gt;; JS embeds asynchronously and calls &lt;em&gt;back&lt;/em&gt; into a Java &lt;code&gt;@JSExport&lt;/code&gt; with the finished vector. Across the boundary we pass only &lt;code&gt;String&lt;/code&gt; and &lt;code&gt;int&lt;/code&gt;/&lt;code&gt;double&lt;/code&gt; (vectors travel as comma-separated strings), because those primitive types marshal reliably on WasmGC. This pattern — &lt;em&gt;Java plans, JavaScript awaits, Java computes&lt;/em&gt; — recurs in indexing, semantic compression, and agent orchestration alike.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. The serverless architecture
&lt;/h2&gt;

&lt;p&gt;"Serverless" here is meant literally — not "someone else's servers (FaaS)," but &lt;strong&gt;no servers at all&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The build pipeline compiles &lt;code&gt;RAGOrchestrator.java&lt;/code&gt; and friends to &lt;code&gt;classes.wasm&lt;/code&gt; + a runtime loader, and a GitHub Actions workflow publishes the static bundle (&lt;code&gt;index.html&lt;/code&gt;, &lt;code&gt;app.js&lt;/code&gt;, the Wasm artifacts) straight to a CDN (GitHub Pages) with no branch tracking. There is no origin server in the request path. The "deployment" is a handful of static files behind a CDN edge.&lt;/p&gt;

&lt;p&gt;Everything that a traditional stack would put on the server now lives in the tab:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Traditional tier&lt;/th&gt;
&lt;th&gt;In this architecture&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;API gateway / app server&lt;/td&gt;
&lt;td&gt;Java core compiled to Wasm&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vector database (Pinecone, etc.)&lt;/td&gt;
&lt;td&gt;sqlite-vec (WASM) + a Java cosine store&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Embedding service&lt;/td&gt;
&lt;td&gt;Transformers.js on the local CPU&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LLM API (OpenAI, etc.)&lt;/td&gt;
&lt;td&gt;WebLLM on the local GPU&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Durable storage (Postgres)&lt;/td&gt;
&lt;td&gt;IndexedDB / OPFS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CDN&lt;/td&gt;
&lt;td&gt;CDN (the only thing left)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The economic and privacy consequences fall straight out of the diagram: &lt;strong&gt;marginal cost per inference is zero&lt;/strong&gt;, and &lt;strong&gt;no user data ever leaves the device&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Local intelligence: WebGPU, on-device embeddings, and a browser LLM
&lt;/h2&gt;

&lt;p&gt;The "intelligence" tier is where 2026's browser capabilities earn their keep.&lt;/p&gt;

&lt;h3&gt;
  
  
  6.1 WebGPU
&lt;/h3&gt;

&lt;p&gt;WebGPU is the modern successor to WebGL — a low-level API that exposes the GPU for both rendering &lt;em&gt;and&lt;/em&gt; general-purpose &lt;strong&gt;compute shaders&lt;/strong&gt;. It is what makes practical LLM inference in a tab possible: the matrix multiplications at the heart of a transformer map onto GPU compute kernels.&lt;/p&gt;

&lt;h3&gt;
  
  
  6.2 WebLLM + a quantized SLM
&lt;/h3&gt;

&lt;p&gt;We run &lt;strong&gt;&lt;code&gt;Qwen2.5-0.5B-Instruct&lt;/code&gt;&lt;/strong&gt; quantized to &lt;code&gt;q4f16_1&lt;/code&gt; (4-bit weights, ~945 MB VRAM) via &lt;strong&gt;&lt;a href="https://github.com/mlc-ai/web-llm" rel="noopener noreferrer"&gt;WebLLM&lt;/a&gt;&lt;/strong&gt; (MLC). The model weights download once into the browser cache; thereafter inference is fully local and offline-capable. Tokens stream from the GPU directly into the DOM:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;engine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;webllm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CreateMLCEngine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Qwen2.5-0.5B-Instruct-q4f16_1-MLC&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;initProgressCallback&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;await &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;chunk&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;outputBox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]?.&lt;/span&gt;&lt;span class="nx"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  6.3 On-device embeddings
&lt;/h3&gt;

&lt;p&gt;Retrieval needs vectors. &lt;strong&gt;Transformers.js&lt;/strong&gt; runs &lt;code&gt;Xenova/all-MiniLM-L6-v2&lt;/code&gt; (384-dimensional, mean-pooled, L2-normalized) on the CPU via ONNX-runtime-web — a ~25 MB model that produces sentence embeddings in milliseconds, entirely client-side.&lt;/p&gt;

&lt;p&gt;The division of labor is deliberate: &lt;strong&gt;embeddings on the CPU&lt;/strong&gt; (small, frequent, latency-sensitive), &lt;strong&gt;generation on the GPU&lt;/strong&gt; (large, occasional, throughput-bound), &lt;strong&gt;everything else in Wasm&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. The combined architecture: serverless RAG
&lt;/h2&gt;

&lt;p&gt;Putting the tiers together yields a full Retrieval-Augmented Generation loop with no server in sight.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[User uploads text / PDF]
        │
        ▼
┌───────────────────────────────┐
│  JAVA WASM CORE                │  1. chunk the document (sliding window)
└───────┬───────────────────────┘
        │  emit each chunk ──► JS
        ▼
┌───────────────────────────────┐
│  JS: Transformers.js           │  2. embed each chunk → float[384]
└───────┬───────────────────────┘
        │  vector (CSV) ──► back into Wasm
        ▼
┌───────────────────────────────┐
│  JAVA WASM CORE                │  3. index the vector
│                                │  4. on query: cosine rank + top-K
│                                │  5. compress context (Headroom-style)
└───────┬───────────────────────┘
        │  assembled prompt ──► JS
        ▼
┌───────────────────────────────┐
│  JS: WebLLM (WebGPU)           │  6. stream the answer locally
└───────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On top of this spine the reference implementation layers several capabilities, each chosen to demonstrate that &lt;em&gt;real&lt;/em&gt; work — not glue — runs in Java/Wasm:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Persistent memory.&lt;/strong&gt; A "remember/recall" facility backed by &lt;strong&gt;sqlite-vec&lt;/strong&gt; (a vector-search SQLite extension compiled to WASM) for KNN, with &lt;strong&gt;IndexedDB&lt;/strong&gt; as the durable layer; the store is rehydrated into the engine on every boot, so memory survives closing the browser. The Java core orchestrates it; a brute-force JS cosine store stands by as a fallback so the feature degrades gracefully if the WASM engine fails to load.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context compression&lt;/strong&gt; ("Headroom-style"). Before the prompt reaches the model, Java trims retrieved context to the query-relevant sentences, drops near-duplicates (Jaccard), and enforces a token budget — in two modes: &lt;strong&gt;lexical&lt;/strong&gt; (term-overlap scoring) and &lt;strong&gt;semantic&lt;/strong&gt; (Java cosine-scores each sentence against the query embedding). Less noise for a small model; fewer tokens for a tight context window.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A multi-agent society.&lt;/strong&gt; A &lt;em&gt;Supervisor&lt;/em&gt; implemented in Java plans a pipeline and authors the role prompts for &lt;em&gt;Researcher → Coder → Critic&lt;/em&gt; agents; JavaScript runs the asynchronous LLM turns; Java merges the outputs. The orchestration logic is Wasm; the inference is WebGPU.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A semantic map.&lt;/strong&gt; Java implements &lt;strong&gt;PCA&lt;/strong&gt; (top-2 components via power iteration, never materializing the 384×384 covariance) and &lt;strong&gt;k-means&lt;/strong&gt;, from scratch, on the chunk embeddings, then draws an interactive, topic-clustered 2D scatter on an HTML &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt; — real machine learning &lt;em&gt;and&lt;/em&gt; graphics, both inside the Wasm core.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A UI built in WebAssembly.&lt;/strong&gt; A live dashboard whose DOM is created, styled, and event-wired entirely from Java via TeaVM's JSO DOM API — proof that Wasm can own presentation, not just computation.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  7.1 Why the Java core is genuine business logic, not a wrapper
&lt;/h3&gt;

&lt;p&gt;A fair skeptic asks: &lt;em&gt;is Java actually doing anything, or just relaying?&lt;/em&gt; The answer is concrete. The cosine similarity, the top-K ranking, the sliding-window chunker, the sentence-level compression scoring, the PCA eigen-decomposition, the k-means clustering, and the agent merge are all &lt;strong&gt;pure Java numerical/algorithmic code executing in the &lt;code&gt;.wasm&lt;/code&gt;&lt;/strong&gt;. The system even surfaces this: a "Wasm Core Activity" panel streams lines emitted from &lt;em&gt;inside&lt;/em&gt; the module (e.g. cosine scores that exist nowhere else), and a &lt;code&gt;selfTest()&lt;/code&gt; export computes a known value to prove the compiled code is live.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. Analysis
&lt;/h2&gt;

&lt;h3&gt;
  
  
  8.1 Privacy
&lt;/h3&gt;

&lt;p&gt;The strongest property is structural, not promised: &lt;strong&gt;data cannot leak because it never has anywhere to go.&lt;/strong&gt; Documents, embeddings, memories, and prompts live in the tab's heap, IndexedDB, and GPU memory. The only network traffic is the one-time, cacheable download of static assets and model weights from a CDN. This is a qualitatively different privacy posture than "we don't train on your data."&lt;/p&gt;

&lt;h3&gt;
  
  
  8.2 Cost
&lt;/h3&gt;

&lt;p&gt;Capital and marginal cost of inference is &lt;strong&gt;zero&lt;/strong&gt; to the operator; compute is donated by the client's own hardware. Hosting is a static CDN bucket. This changes which products are viable — features whose per-call economics would sink a server-backed app are free here.&lt;/p&gt;

&lt;h3&gt;
  
  
  8.3 Performance &amp;amp; honest trade-offs
&lt;/h3&gt;

&lt;p&gt;We are deliberately not selling a miracle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Model capability.&lt;/strong&gt; A 0.5B-parameter model is not GPT-class. It is excellent for grounded, context-bounded RAG answers and demonstrations; it is not a general reasoner. The architecture is model-agnostic — swap in a larger quantized model as client GPUs allow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cold start.&lt;/strong&gt; The first visit downloads model weights (hundreds of MB). Subsequent loads are cache-fast and offline-capable, but the first run is heavy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hardware floor.&lt;/strong&gt; WebGPU requires a reasonably modern browser/GPU (Chrome/Edge 113+). Where WebGPU is absent, retrieval and embeddings still work; generation is disabled.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lexical vs semantic compression.&lt;/strong&gt; Term-overlap compression is fast but can drop a relevant sentence on vocabulary mismatch; the semantic mode fixes that at the cost of N extra embedding calls.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ecosystem maturity.&lt;/strong&gt; The bleeding edge bites. We hit a real Emscripten 4.0.0 &lt;code&gt;postRun&lt;/code&gt; regression in one sqlite-vec WASM build and had to move to a build carrying the upstream fix — and we keep a JS fallback precisely because browser-WASM library stability is still uneven.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are engineering constraints to plan around, not refutations of the thesis.&lt;/p&gt;




&lt;h2&gt;
  
  
  9. When to reach for this architecture
&lt;/h2&gt;

&lt;p&gt;This pattern is a strong fit when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;privacy is paramount&lt;/strong&gt; (legal, medical, financial, personal, on-device enterprise data);&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;per-inference cost must be zero or near-zero&lt;/strong&gt; at scale;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;offline or air-gapped operation&lt;/strong&gt; is valuable;&lt;/li&gt;
&lt;li&gt;you want to &lt;strong&gt;reuse a mature, typed business-logic codebase&lt;/strong&gt; (Java/Kotlin/Rust) rather than rewrite it in JS;&lt;/li&gt;
&lt;li&gt;the task is &lt;strong&gt;bounded and retrieval-grounded&lt;/strong&gt; rather than open-ended frontier reasoning.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is a poor fit when you genuinely need frontier-model capability, must support thin/old clients with no WebGPU, or require centralized data aggregation.&lt;/p&gt;




&lt;h2&gt;
  
  
  10. Future directions
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Larger local models&lt;/strong&gt; as quantization and client GPUs improve (1.5B–3B class in the same architecture).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OPFS-backed persistence&lt;/strong&gt; to drop the IndexedDB rehydration step and keep a true SQLite file on disk.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WebGPU compute from Wasm directly&lt;/strong&gt;, letting the Java core dispatch its own kernels for the heavy linear algebra.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Embedding caches&lt;/strong&gt; so semantic compression and the semantic map avoid recomputation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A portable core&lt;/strong&gt; — the same Wasm business module redeployed unchanged to edge functions and native hosts, fulfilling Wasm's "write once, run at any tier" promise.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  11. Conclusion
&lt;/h2&gt;

&lt;p&gt;The center of gravity of software is shifting back toward the client — not because the cloud failed, but because the browser quietly became a capable, GPU-accelerated, multi-language runtime. &lt;strong&gt;WebAssembly&lt;/strong&gt; lets us put a real, statically-typed business core — written in &lt;strong&gt;Java&lt;/strong&gt; — on that runtime as a lean, sandboxed, near-native binary. &lt;strong&gt;WebGPU&lt;/strong&gt; puts a language model on the same tab's GPU. Stitched together with on-device embeddings and an in-browser vector database, the result is an AI application that is &lt;strong&gt;private by construction, free to operate, and dependent on no server at all.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The reference implementation proves it is buildable today, sharp edges and all. The interesting question is no longer &lt;em&gt;whether&lt;/em&gt; the full AI stack can live in a browser tab — it can — but &lt;em&gt;which&lt;/em&gt; applications should. For anything where privacy, cost, or offline operation matters, the answer is increasingly: this one.&lt;/p&gt;




&lt;h2&gt;
  
  
  Appendix: reference stack
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Technology&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Business core&lt;/td&gt;
&lt;td&gt;Java 17 → &lt;strong&gt;TeaVM 0.15&lt;/strong&gt; (WasmGC)&lt;/td&gt;
&lt;td&gt;Chunking, vectors, retrieval, compression, agents, UI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LLM inference&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;WebLLM&lt;/strong&gt; (MLC) + &lt;code&gt;Qwen2.5-0.5B-Instruct-q4f16_1&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Local generation on WebGPU&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Embeddings&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Transformers.js&lt;/strong&gt; + &lt;code&gt;Xenova/all-MiniLM-L6-v2&lt;/code&gt; (384-d)&lt;/td&gt;
&lt;td&gt;On-device CPU vectors&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vector engine&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;sqlite-vec&lt;/strong&gt; (WASM) &lt;code&gt;vec0&lt;/code&gt; KNN + Java cosine fallback&lt;/td&gt;
&lt;td&gt;Similarity search&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Durable storage&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;IndexedDB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Persistent memory&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UI / graphics&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TeaVM JSO DOM + Canvas&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;DOM and the semantic-map canvas, built in Java&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Delivery&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;GitHub Actions → GitHub Pages (CDN)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Static, serverless deployment&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Repository:&lt;/strong&gt; &lt;a href="https://github.com/vishalmysore/javaWASM" rel="noopener noreferrer"&gt;github.com/vishalmysore/javaWASM&lt;/a&gt; — Apache-2.0.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This whitepaper documents a working system; its architecture, code snippets, and trade-offs are taken directly from the reference implementation.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>java</category>
      <category>llm</category>
      <category>rag</category>
    </item>
    <item>
      <title>webSLM: Fine-tuning, Compiling, and Running Domain-Specific Small Language Models Entirely in the Browser</title>
      <dc:creator>vishalmysore</dc:creator>
      <pubDate>Mon, 29 Jun 2026 13:42:20 +0000</pubDate>
      <link>https://dev.to/vishalmysore/webslm-fine-tuning-compiling-and-running-domain-specific-small-language-models-entirely-in-the-1i5i</link>
      <guid>https://dev.to/vishalmysore/webslm-fine-tuning-compiling-and-running-domain-specific-small-language-models-entirely-in-the-1i5i</guid>
      <description>&lt;p&gt;webSLM is an end-to-end pipeline for turning a general-purpose Small Language Model (SLM) into a &lt;strong&gt;domain-specialized assistant that runs 100% in the browser&lt;/strong&gt; — no server, no API key, no inference cost, full offline capability after first load. This paper documents the complete lifecycle of a worked example, &lt;code&gt;WebSLM-Medical-0.5B&lt;/code&gt;: (1) &lt;strong&gt;LoRA fine-tuning&lt;/strong&gt; of &lt;code&gt;Qwen2.5-0.5B-Instruct&lt;/code&gt; on a small domain dataset using a free Colab T4; (2) &lt;strong&gt;compilation and 4-bit quantization&lt;/strong&gt; to a WebGPU model library via a reproducible, GPU-free GitHub Actions workflow built on MLC-LLM v0.19.0; and (3) &lt;strong&gt;in-browser execution&lt;/strong&gt; through WebLLM, including the non-obvious runtime-integration details that determine whether the model loads at all.&lt;/p&gt;

&lt;p&gt;We also report a controlled A/B validation showing what fine-tuning on a small dataset actually changes — and what it does not — and document two decoding pitfalls (greedy repetition loops and penalty-induced language drift on bilingual base models) that materially affect output quality on sub-1B models.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Introduction
&lt;/h2&gt;

&lt;p&gt;The dominant narrative in language modeling has been scale. But a parallel track — Small Language Models in the 0.5B–7B range, &lt;em&gt;designed&lt;/em&gt; to be efficient rather than pruned down — has matured to the point where a properly fine-tuned 0.5B–1.5B model delivers genuinely useful behavior in a focused domain. At the same time, &lt;strong&gt;WebGPU&lt;/strong&gt; has made it possible to run these models directly inside a browser tab, on the user's own hardware.&lt;/p&gt;

&lt;p&gt;The gap webSLM closes is not a research problem; it is an &lt;strong&gt;engineering and tooling problem&lt;/strong&gt;. Getting from a Hugging Face checkpoint to a working in-browser, domain-specialized chatbot requires stitching together three independently fiddly stages — fine-tuning, MLC compilation/quantization, and WebLLM runtime wiring — each with version-sensitive environments and undocumented failure modes. This paper is the field manual for that path.&lt;/p&gt;

&lt;h3&gt;
  
  
  1.1 Design principles
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Model-agnostic.&lt;/strong&gt; Any MLC-LLM-supported base works (Qwen2/2.5, Llama-3.x, Gemma-2, Phi-3.5, Mistral). Nothing in the pipeline is specific to one architecture.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Training and compilation are separate steps, on purpose.&lt;/strong&gt; Fine-tuning needs a GPU (Colab); compilation is CPU-only codegen (CI). The two never run in the same environment, so neither inherits the other's dependency constraints.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reproducible, GPU-free builds.&lt;/strong&gt; The expensive, error-prone compilation runs in GitHub Actions with a pinned, from-source toolchain — no local Linux GPU box required.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Browser-first deployment.&lt;/strong&gt; The artifact is static files (weight shards + a &lt;code&gt;.wasm&lt;/code&gt;) on a CDN; the client is one HTML page.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  2. Background
&lt;/h2&gt;

&lt;h3&gt;
  
  
  2.1 SLM vs. quantized LLM
&lt;/h3&gt;

&lt;p&gt;These are frequently conflated but are different answers to the same "large models are expensive" problem. An &lt;strong&gt;SLM&lt;/strong&gt; is small &lt;em&gt;by design&lt;/em&gt; — its efficiency comes from architecture and curated training data. A &lt;strong&gt;quantized LLM&lt;/strong&gt; is a large model compressed &lt;em&gt;after&lt;/em&gt; training; it keeps the full parameter structure and capability profile of the original, just at lower numeric precision. For the browser, only the SLM path is viable: a 1.5B model uses ~1–2 GB of GPU memory, while an INT4-quantized 70B model still needs 30–40 GB. They are not in the same deployment category.&lt;/p&gt;

&lt;p&gt;Note that webSLM uses quantization &lt;strong&gt;on top of&lt;/strong&gt; an SLM: the 0.5B model is itself quantized to 4-bit (q4f16_1) for the browser. Quantization here is a deployment compression, not the source of "smallness."&lt;/p&gt;

&lt;h3&gt;
  
  
  2.2 Fine-tuned SLM vs. RAG
&lt;/h3&gt;

&lt;p&gt;RAG (Retrieval-Augmented Generation) is a &lt;em&gt;system architecture&lt;/em&gt;, not a model type: it injects retrieved documents into the prompt at query time. It excels at large, dynamic knowledge bases but requires a retrieval layer (embeddings, vector store, ingestion) — infrastructure that does not exist in a pure browser deployment. Fine-tuning encodes &lt;strong&gt;behavior, style, and domain patterns into the weights&lt;/strong&gt; instead. The two are complementary; for the serverless browser case, fine-tuning is the only specialization mechanism available. (The companion demo includes an optional in-browser TF-IDF RAG path for comparison, but the model itself carries no retrieval.)&lt;/p&gt;

&lt;h3&gt;
  
  
  2.3 The runtime stack: WebLLM + MLC-LLM
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;WebLLM&lt;/strong&gt; (&lt;a href="https://github.com/mlc-ai/web-llm" rel="noopener noreferrer"&gt;github.com/mlc-ai/web-llm&lt;/a&gt;) is the browser runtime. It executes models on &lt;strong&gt;WebGPU&lt;/strong&gt; and exposes an OpenAI-compatible JS API (&lt;code&gt;engine.chat.completions.create()&lt;/code&gt;), with streaming, fully local. It &lt;strong&gt;cannot&lt;/strong&gt; load a raw Hugging Face checkpoint.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MLC-LLM&lt;/strong&gt; (part of the Apache TVM ecosystem) is the compiler. For a browser target it produces two things WebLLM consumes:

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Quantized weight shards&lt;/strong&gt; (&lt;code&gt;params_shard_*.bin&lt;/code&gt;) plus a manifest (&lt;code&gt;ndarray-cache.json&lt;/code&gt;) and chat/sampling config (&lt;code&gt;mlc-chat-config.json&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;&lt;code&gt;.wasm&lt;/code&gt; model library&lt;/strong&gt; containing the compiled compute kernels for that &lt;em&gt;specific&lt;/em&gt; architecture.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The critical, easily-missed consequence: &lt;strong&gt;the &lt;code&gt;.wasm&lt;/code&gt; is tied to the runtime ABI.&lt;/strong&gt; A model library compiled with MLC-LLM v0.19.0 must be loaded by the matching &lt;code&gt;@mlc-ai/web-llm&lt;/code&gt; build (0.2.79). Mismatched versions fail to instantiate the wasm.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. System architecture
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  domain data (JSONL chat)
        │
        │   STAGE 1 — Colab T4 (GPU)
        │   finetune/train_lora.py:  LoRA SFT → merge_and_unload → push
        ▼
  merged HF checkpoint                       e.g. VishalMysore/WebSLM-Medical-0.5B
  (standard fp16 safetensors)                (qwen2, 0.5B, full weights)
        │
        │   STAGE 2 — GitHub Actions (CPU only)
        │   normalize_config → convert_weight (q4f16_1) → gen_config → compile --device webgpu
        ▼
  MLC artifacts                              VishalMysore/WebSLM-Custom-MLC
  (params_shard_*.bin + .wasm + configs)     (8 shards ~278 MB + 3.7 MB wasm)
        │
        │   STAGE 3 — Any WebGPU browser (client GPU)
        │   @mlc-ai/web-llm@0.2.79: MLCEngine(appConfig) → reload → chat.completions
        ▼
  in-browser domain assistant               webSLMDemo (GitHub Pages)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three environments, three hardware profiles, zero shared dependencies. The handoff between stages is always a plain artifact (an HF repo), never a live process.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Stage 1 — Fine-tuning on Colab (LoRA SFT)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  4.1 Data format
&lt;/h3&gt;

&lt;p&gt;Training data is &lt;strong&gt;chat/conversational JSONL&lt;/strong&gt; — one JSON object per line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"messages"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"system"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"You are a careful medical information assistant. Provide general, educational health information in plain language, and always recommend consulting a licensed healthcare professional for diagnosis or treatment."&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"What are common signs of dehydration?"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"assistant"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Common signs include increased thirst, dry mouth, dark yellow urine, reduced urination, fatigue, dizziness, and headache. Severe dehydration can cause confusion or a rapid heartbeat and needs urgent care. Drink fluids and seek medical help if symptoms are severe or persist."&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two design rules carry most of the signal:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Keep the &lt;code&gt;system&lt;/code&gt; message identical across the dataset.&lt;/strong&gt; The model learns a stable persona from it. (This system prompt becomes the one you should use at inference — see §6.4 and §7.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The &lt;code&gt;assistant&lt;/code&gt; turns are exactly what the model imitates.&lt;/strong&gt; Their length, register, and structure &lt;em&gt;are&lt;/em&gt; the behavior you are training. The worked example's answers are short, plain-language, and always close with a referral to a professional — and that is precisely the signature the fine-tune learns.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The repository ships three &lt;strong&gt;illustrative starter sets&lt;/strong&gt; in &lt;code&gt;finetune/data/&lt;/code&gt; (&lt;code&gt;medical.jsonl&lt;/code&gt; ≈ 34 examples, plus &lt;code&gt;legal&lt;/code&gt; and &lt;code&gt;insurance&lt;/code&gt;). These are deliberately tiny: they prove the pipeline, they do &lt;strong&gt;not&lt;/strong&gt; fully specialize a model. Real domain quality needs hundreds to thousands of examples.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.2 The training recipe
&lt;/h3&gt;

&lt;p&gt;Fine-tuning uses &lt;strong&gt;LoRA&lt;/strong&gt; (Low-Rank Adaptation) via TRL's &lt;code&gt;SFTTrainer&lt;/code&gt;, rendering each example through the base model's chat template before training. The merged result is a standard HF checkpoint the build stage can consume directly.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Hyperparameter&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Base model&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Qwen/Qwen2.5-0.5B-Instruct&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;any Instruct SLM; &lt;code&gt;arch=qwen2&lt;/code&gt;, &lt;code&gt;conv=qwen2&lt;/code&gt; flow straight to build&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LoRA rank &lt;code&gt;r&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LoRA &lt;code&gt;alpha&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LoRA dropout&lt;/td&gt;
&lt;td&gt;0.05&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Target modules&lt;/td&gt;
&lt;td&gt;&lt;code&gt;q_proj, k_proj, v_proj, o_proj, gate_proj, up_proj, down_proj&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;all attention + MLP projections&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Epochs&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Learning rate&lt;/td&gt;
&lt;td&gt;2e-4&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Per-device batch&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Grad accumulation&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;effective batch ≈ 16&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Max sequence length&lt;/td&gt;
&lt;td&gt;1024&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Precision&lt;/td&gt;
&lt;td&gt;fp16&lt;/td&gt;
&lt;td&gt;QLoRA (&lt;code&gt;--bits 4&lt;/code&gt;, bitsandbytes) available for larger bases on 16 GB GPUs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Packing&lt;/td&gt;
&lt;td&gt;off&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The core of &lt;code&gt;train_lora.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;tok&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AutoTokenizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_pretrained&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;trust_remote_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ds&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;load_dataset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data_files&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;train&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ds&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;tok&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;apply_chat_template&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;messages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;tokenize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)},&lt;/span&gt;
             &lt;span class="n"&gt;remove_columns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;column_names&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;lora&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LoraConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lora_alpha&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lora_dropout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.05&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bias&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;none&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                  &lt;span class="n"&gt;task_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CAUSAL_LM&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                  &lt;span class="n"&gt;target_modules&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;q_proj&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;k_proj&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;v_proj&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;o_proj&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                  &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gate_proj&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;up_proj&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;down_proj&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="n"&gt;trainer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SFTTrainer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;train_dataset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;sft_cfg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                     &lt;span class="n"&gt;peft_config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;lora&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;processing_class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;tok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# falls back to tokenizer= on older TRL
&lt;/span&gt;&lt;span class="n"&gt;trainer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;train&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After training, the adapter is &lt;strong&gt;merged&lt;/strong&gt; back into the base in fp16 and saved as a self-contained checkpoint (this is mandatory — MLC-LLM's &lt;code&gt;convert_weight&lt;/code&gt; expects a fused model, not LoRA deltas):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;base&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AutoModelForCausalLM&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_pretrained&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;torch_dtype&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;float16&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;merged&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PeftModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_pretrained&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;adapter_dir&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;merge_and_unload&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;merged&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save_pretrained&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;safe_serialization&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;tok&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save_pretrained&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;               &lt;span class="c1"&gt;# carry the tokenizer so the dir is self-contained
&lt;/span&gt;&lt;span class="n"&gt;merged&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push_to_hub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;yourname/WebSLM-Medical-0.5B&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;If you trained a LoRA adapter elsewhere (axolotl, Unsloth, raw PEFT), &lt;code&gt;merge_lora.py&lt;/code&gt; does just the merge+export step. If you did a &lt;em&gt;full&lt;/em&gt; fine-tune (no LoRA) you already have a standard checkpoint — skip merging entirely.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  4.3 Running it on Colab
&lt;/h3&gt;

&lt;p&gt;The fastest path is the clone-and-run notebook &lt;code&gt;finetune/finetune_webslm_colab.ipynb&lt;/code&gt; (Runtime → &lt;strong&gt;T4 GPU&lt;/strong&gt;). It clones the repo, installs &lt;code&gt;finetune/requirements.txt&lt;/code&gt;, logs into Hugging Face, trains on the chosen domain, and &lt;strong&gt;pushes the merged model to your HF account&lt;/strong&gt;. On a T4, a few hundred examples train in well under an hour. The final cell prints exactly what to enter in the build Action.&lt;/p&gt;

&lt;p&gt;Equivalent CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; finetune/requirements.txt
python finetune/train_lora.py &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--base&lt;/span&gt; Qwen/Qwen2.5-0.5B-Instruct &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--data&lt;/span&gt; finetune/data/medical.jsonl &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--push-merged&lt;/span&gt; yourname/WebSLM-Medical-0.5B &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--epochs&lt;/span&gt; 3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output of Stage 1 is a plain fp16 HF model repo — e.g. &lt;code&gt;VishalMysore/WebSLM-Medical-0.5B&lt;/code&gt; (qwen2 architecture, single &lt;code&gt;model.safetensors&lt;/code&gt;, tokenizer, configs). Nothing browser-specific yet.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Stage 2 — Compiling to WebGPU via GitHub Actions
&lt;/h2&gt;

&lt;p&gt;This is the stage that, done by hand, costs newcomers a day or more. webSLM encodes the entire toolchain build and the three-command MLC pipeline into &lt;code&gt;.github/workflows/build-slm.yml&lt;/code&gt;, triggered manually (&lt;code&gt;workflow_dispatch&lt;/code&gt;) with a domain preset &lt;strong&gt;or&lt;/strong&gt; a Custom path pointing at your fine-tune.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.1 Why build the toolchain from source
&lt;/h3&gt;

&lt;p&gt;The workflow builds &lt;strong&gt;MLC-LLM v0.19.0 and TVM from source&lt;/strong&gt;, because the MLC nightly wheels are broken by the in-progress &lt;code&gt;apache-tvm-ffi&lt;/code&gt; migration. The compile step is &lt;strong&gt;CPU-only&lt;/strong&gt;: &lt;code&gt;mlc_llm compile --device webgpu&lt;/code&gt; is code generation via emscripten — it emits WebAssembly kernels and never needs a GPU. A full toolchain build is ~35–45 minutes; the workflow has a 350-minute ceiling and frees disk space first.&lt;/p&gt;

&lt;p&gt;Build environment (the parts that matter):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Pin / setting&lt;/th&gt;
&lt;th&gt;Reason&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;MLC-LLM&lt;/td&gt;
&lt;td&gt;v0.19.0, from source&lt;/td&gt;
&lt;td&gt;nightly wheels broken (tvm-ffi migration)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TVM&lt;/td&gt;
&lt;td&gt;bundled &lt;code&gt;3rdparty/tvm&lt;/code&gt;, from source&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;USE_LLVM "llvm-config --link-static"&lt;/code&gt;, &lt;code&gt;HIDE_PRIVATE_SYMBOLS ON&lt;/code&gt;, all GPU backends OFF&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LLVM&lt;/td&gt;
&lt;td&gt;system &lt;code&gt;llvm-dev&lt;/code&gt; + matching &lt;code&gt;libpolly-*-dev&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;TVM static-links LLVM incl. Polly; &lt;code&gt;llvm-dev&lt;/code&gt; doesn't ship &lt;code&gt;libPolly.a&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;emscripten&lt;/td&gt;
&lt;td&gt;3.1.56&lt;/td&gt;
&lt;td&gt;wasm toolchain matching MLC-LLM's web runtime&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rust&lt;/td&gt;
&lt;td&gt;latest stable (≥1.85)&lt;/td&gt;
&lt;td&gt;modern deps; &lt;code&gt;tokenizers-cpp&lt;/code&gt; needs &lt;code&gt;--cap-lints=allow&lt;/code&gt; on new Rust&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A subtle linking detail handled by the workflow: &lt;code&gt;mlc_llm compile --device webgpu&lt;/code&gt; links several wasm bitcode runtimes — &lt;code&gt;mlc_wasm_runtime.bc&lt;/code&gt; (from &lt;code&gt;mlc-llm/web&lt;/code&gt;) and &lt;code&gt;wasm_runtime.bc&lt;/code&gt; / &lt;code&gt;tvmjs_support.bc&lt;/code&gt; / &lt;code&gt;webgpu_runtime.bc&lt;/code&gt; (from &lt;code&gt;tvm/web&lt;/code&gt;). The latter three are built and copied into TVM's &lt;code&gt;build/&lt;/code&gt; so its &lt;code&gt;find_lib_path&lt;/code&gt; discovers them.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.2 The MLC compile pipeline
&lt;/h3&gt;

&lt;p&gt;Once the toolchain exists, the actual conversion is three commands (mirrored in &lt;code&gt;build.sh&lt;/code&gt; for local/WSL2 runs), preceded by a config-normalization step:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1b. Make a freshly-merged (newer-transformers) config readable by mlc-llm v0.19.0&lt;/span&gt;
python normalize_config.py &lt;span class="s2"&gt;"hf/&lt;/span&gt;&lt;span class="nv"&gt;$NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# 2. Quantize + shard the weights  (HF -&amp;gt; MLC params)&lt;/span&gt;
mlc_llm convert_weight &lt;span class="s2"&gt;"hf/&lt;/span&gt;&lt;span class="nv"&gt;$NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--quantization&lt;/span&gt; q4f16_1 &lt;span class="nt"&gt;--model-type&lt;/span&gt; qwen2 &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$W&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# 3. Emit chat template + tokenizer + model metadata&lt;/span&gt;
mlc_llm gen_config &lt;span class="s2"&gt;"hf/&lt;/span&gt;&lt;span class="nv"&gt;$NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--quantization&lt;/span&gt; q4f16_1 &lt;span class="nt"&gt;--model-type&lt;/span&gt; qwen2 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--conv-template&lt;/span&gt; qwen2 &lt;span class="nt"&gt;--prefill-chunk-size&lt;/span&gt; 1024 &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$W&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# 4. Codegen the WebGPU model library&lt;/span&gt;
mlc_llm compile &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$W&lt;/span&gt;&lt;span class="s2"&gt;/mlc-chat-config.json"&lt;/span&gt; &lt;span class="nt"&gt;--device&lt;/span&gt; webgpu &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$L&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;$NAME&lt;/span&gt;&lt;span class="s2"&gt;-q4f16_1-webgpu.wasm"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  5.2.1 The config-normalization gotcha
&lt;/h4&gt;

&lt;p&gt;Recent &lt;code&gt;transformers&lt;/code&gt; releases changed the config schema: the RoPE base moved from a top-level &lt;code&gt;rope_theta&lt;/code&gt; into a nested &lt;code&gt;rope_parameters&lt;/code&gt;/&lt;code&gt;rope_scaling&lt;/code&gt; dict, and &lt;code&gt;torch_dtype&lt;/code&gt; became &lt;code&gt;dtype&lt;/code&gt;. MLC-LLM v0.19.0 still expects the old top-level keys, so &lt;code&gt;convert_weight&lt;/code&gt; on a freshly fine-tuned model fails with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TypeError: QWen2Config.__init__() missing 1 required positional argument: 'rope_theta'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;normalize_config.py&lt;/code&gt; hoists &lt;code&gt;rope_theta&lt;/code&gt; back to the top level and restores &lt;code&gt;torch_dtype&lt;/code&gt; — decoupling your &lt;em&gt;training&lt;/em&gt; transformers version from the &lt;em&gt;pinned, old&lt;/em&gt; build compiler. It is idempotent and safe on already-old configs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rope_theta&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rope_parameters&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rope_scaling&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;rp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;rp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rope_theta&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rope_theta&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rp&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rope_theta&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;torch_dtype&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;cfg&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dtype&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;torch_dtype&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dtype&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the kind of failure that, undiagnosed, produces a silent crash or garbage output with no upstream documentation.&lt;/p&gt;

&lt;h4&gt;
  
  
  5.2.2 Quantization choice
&lt;/h4&gt;

&lt;p&gt;Default is &lt;strong&gt;&lt;code&gt;q4f16_1&lt;/code&gt;&lt;/strong&gt; (4-bit weights, fp16 activations) — the smallest practical format. Some models &lt;strong&gt;overflow fp16 to NaN&lt;/strong&gt; during inference (observed with TinyLlama-1.1B); for those, fall back to &lt;strong&gt;&lt;code&gt;q4f32_1&lt;/code&gt;&lt;/strong&gt; (fp32 activations), trading size for numerical stability. The selected format is part of the wasm filename and the &lt;code&gt;model_id&lt;/code&gt;, so it must match between the compiled artifact and the browser config.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.3 Inputs and outputs
&lt;/h3&gt;

&lt;p&gt;The workflow exposes domain presets (Qwen2.5-Coder-1.5B for code, Qwen2.5-Math-1.5B for math, general Qwen/Llama/Gemma/Phi bases) and a &lt;strong&gt;Custom&lt;/strong&gt; path. For a fine-tune you select &lt;strong&gt;Custom&lt;/strong&gt; and pass:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="py"&gt;domain&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;Custom&lt;/span&gt;
&lt;span class="py"&gt;custom_model_hf&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;VishalMysore/WebSLM-Medical-0.5B&lt;/span&gt;
&lt;span class="py"&gt;custom_arch&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;qwen2&lt;/span&gt;
&lt;span class="py"&gt;custom_conv&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;qwen2&lt;/span&gt;
&lt;span class="py"&gt;quant&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;q4f16_1&lt;/span&gt;
&lt;span class="py"&gt;custom_name&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;WebSLM-Custom        (becomes the output repo/lib name)&lt;/span&gt;
&lt;span class="py"&gt;upload_hf&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;true                 (push artifacts to HF; needs HF_TOKEN + HF_NAMESPACE)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Outputs are saved &lt;strong&gt;unconditionally&lt;/strong&gt; (so a bad HF token never loses a 45-minute build): a downloadable &lt;strong&gt;Actions artifact&lt;/strong&gt;, a &lt;strong&gt;GitHub Release&lt;/strong&gt; carrying the &lt;code&gt;.wasm&lt;/code&gt;, and — when &lt;code&gt;upload_hf&lt;/code&gt; is set — a self-contained &lt;strong&gt;Hugging Face model repo&lt;/strong&gt;. The worked example produced &lt;code&gt;VishalMysore/WebSLM-Custom-MLC&lt;/code&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;File&lt;/th&gt;
&lt;th&gt;Size&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mlc-chat-config.json&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;2 KB&lt;/td&gt;
&lt;td&gt;chat template, sampling defaults, context window&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ndarray-cache.json&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;102 KB&lt;/td&gt;
&lt;td&gt;weight-shard manifest&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;params_shard_0…7.bin&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;~278 MB total&lt;/td&gt;
&lt;td&gt;4-bit quantized weights (8 shards)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;tokenizer.json&lt;/code&gt;, &lt;code&gt;tokenizer_config.json&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;~11 MB&lt;/td&gt;
&lt;td&gt;tokenizer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;libs/WebSLM-Custom-q4f16_1-webgpu.wasm&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;3.7 MB&lt;/td&gt;
&lt;td&gt;WebGPU model library&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Compiled metadata of note: &lt;code&gt;model_type: qwen2&lt;/code&gt;, &lt;code&gt;quantization: q4f16_1&lt;/code&gt;, &lt;code&gt;context_window_size: 32768&lt;/code&gt;, &lt;code&gt;vocab_size: 151936&lt;/code&gt;, &lt;code&gt;conv_template: qwen2&lt;/code&gt;, default sampling &lt;code&gt;temperature: 0.7&lt;/code&gt;, &lt;code&gt;top_p: 0.8&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Stage 3 — Running in the browser with WebLLM
&lt;/h2&gt;

&lt;p&gt;The client is a static page importing &lt;code&gt;@mlc-ai/web-llm&lt;/code&gt;. The model loads from its HF URLs, caches in the browser (Cache API / IndexedDB), and runs on the client GPU. Three integration details determine whether it works at all — each one produced a distinct, opaque error during bring-up.&lt;/p&gt;

&lt;h3&gt;
  
  
  6.1 Registering a custom model — &lt;code&gt;appConfig&lt;/code&gt; goes in the constructor
&lt;/h3&gt;

&lt;p&gt;WebLLM only knows its built-in (prebuilt) models unless you give it an &lt;code&gt;appConfig&lt;/code&gt; describing yours. The &lt;strong&gt;&lt;code&gt;appConfig&lt;/code&gt; must be passed to the &lt;code&gt;MLCEngine&lt;/code&gt; constructor&lt;/strong&gt;, not to &lt;code&gt;reload()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;webllm&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://esm.run/@mlc-ai/web-llm@0.2.79&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;appConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;model_list&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
    &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;     &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://huggingface.co/VishalMysore/WebSLM-Custom-MLC&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;// FULL HF URL&lt;/span&gt;
    &lt;span class="na"&gt;model_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;WebSLM-Custom-q4f16_1-webgpu&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                                   &lt;span class="c1"&gt;// arbitrary local name&lt;/span&gt;
    &lt;span class="na"&gt;model_lib&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://huggingface.co/VishalMysore/WebSLM-Custom-MLC/resolve/main/libs/WebSLM-Custom-q4f16_1-webgpu.wasm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}],&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;engine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;webllm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;MLCEngine&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;appConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;initProgressCallback&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;WebSLM-Custom-q4f16_1-webgpu&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;reload(modelId, chatOpts?)&lt;/code&gt;'s second argument is &lt;code&gt;ChatOptions&lt;/code&gt;, which has &lt;strong&gt;no &lt;code&gt;appConfig&lt;/code&gt; field&lt;/strong&gt; — passing &lt;code&gt;appConfig&lt;/code&gt; there is silently dropped, the engine falls back to its prebuilt list, and you get:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Cannot find model record in appConfig for WebSLM-Custom-q4f16_1-webgpu.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  6.2 &lt;code&gt;model&lt;/code&gt; must be a full URL
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;model_list[].model&lt;/code&gt; field must be a complete &lt;code&gt;https://huggingface.co/{USER}/{REPO}&lt;/code&gt; URL (the four accepted forms all start with &lt;code&gt;https://&lt;/code&gt;). A bare repo id makes WebLLM's internal &lt;code&gt;new URL(...)&lt;/code&gt; throw:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Failed to construct 'URL': Invalid URL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;model_lib&lt;/code&gt; is the full &lt;code&gt;/resolve/main/.../*.wasm&lt;/code&gt; URL; &lt;code&gt;model_id&lt;/code&gt; is a free-form local handle.&lt;/p&gt;

&lt;h3&gt;
  
  
  6.3 Pin the runtime to the wasm's ABI
&lt;/h3&gt;

&lt;p&gt;The import must be &lt;strong&gt;version-pinned&lt;/strong&gt; to the &lt;code&gt;web-llm&lt;/code&gt; build matching the MLC-LLM that produced the wasm:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;webllm&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://esm.run/@mlc-ai/web-llm@0.2.79&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// NOT unpinned (=latest)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;esm.run/@mlc-ai/web-llm&lt;/code&gt; with no version resolves to &lt;em&gt;latest&lt;/em&gt;, whose wasm ABI can differ from a v0.19.0-compiled library — producing instantiation failures at load. The pin table:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Built with&lt;/th&gt;
&lt;th&gt;Runtime&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;MLC-LLM v0.19.0&lt;/td&gt;
&lt;td&gt;&lt;code&gt;@mlc-ai/web-llm@0.2.79&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  6.4 Inference and decoding
&lt;/h3&gt;

&lt;p&gt;WebLLM's API is OpenAI-shaped and streams:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;system&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TRAINING_SYSTEM_PROMPT&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;   &lt;span class="c1"&gt;// match the prompt the model was TRAINED with&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;temperature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;top_p&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;max_tokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;            &lt;span class="c1"&gt;// Qwen2.5's recommended sampling&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;await &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;chunk&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* chunk.choices[0].delta.content */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two points are decisive for output quality on a 0.5B model (see §7.3):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Use the training system prompt at inference.&lt;/strong&gt; The fine-tune's behavior is conditioned on the persona it was trained with; a different system prompt pulls it back toward generic base behavior.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use the model's recommended sampling&lt;/strong&gt; (&lt;code&gt;temperature 0.7&lt;/code&gt;, &lt;code&gt;top_p 0.8&lt;/code&gt;). Greedy decoding and aggressive penalties both degrade small-model output in characteristic ways.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  6.5 The demo application
&lt;/h3&gt;

&lt;p&gt;The companion demo (&lt;a href="https://github.com/vishalmysore/webSLMDemo" rel="noopener noreferrer"&gt;webSLMDemo&lt;/a&gt;, deployed to GitHub Pages) has two modes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Product demo&lt;/strong&gt; — &lt;em&gt;Base + RAG&lt;/em&gt; (left) vs. &lt;em&gt;Fine-tuned SLM&lt;/em&gt; (right). The base panel runs a general model with an in-browser &lt;strong&gt;TF-IDF retriever&lt;/strong&gt; injecting document context; the SLM panel runs the fine-tune with a domain system prompt and no retrieval. This contrasts the two specialization strategies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fine-tuning proof&lt;/strong&gt; — a controlled A/B (next section). It loads the &lt;strong&gt;exact base&lt;/strong&gt; the fine-tune started from (&lt;code&gt;Qwen2.5-0.5B-Instruct-q4f16_1-MLC&lt;/code&gt;, a WebLLM prebuilt at the same quantization) on the left and the fine-tune on the right, feeds &lt;strong&gt;both&lt;/strong&gt; the identical training system prompt with no retrieval, and uses &lt;strong&gt;identical decoding&lt;/strong&gt; — so the only variable is the LoRA training.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  7. Validation: what fine-tuning actually changed
&lt;/h2&gt;

&lt;p&gt;Because the proof mode holds base, prompt, and decoding identical across both panels, any difference is attributable to the LoRA fine-tuning. The following are verbatim in-browser outputs.&lt;/p&gt;

&lt;h3&gt;
  
  
  7.1 In-distribution question (trained topic)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Prompt:&lt;/strong&gt; &lt;em&gt;"What are common signs of dehydration?"&lt;/em&gt; (a question whose topic is in the training set.)&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Base — Qwen2.5-0.5B, no fine-tune:&lt;/strong&gt; "1. Sunken Eyes… 6. Confusion… 9. Dry Skin: Not having much moisture in your skin. 10. Dry Skin… 11. Dry Skin…" — rambling, and falls into a repeat loop to the token limit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fine-tune — WebSLM-Medical-0.5B:&lt;/strong&gt; "Common signs of dehydration include: 1. Sunken eyes 2. Dry mouth and lips … 4. Urine that is dark or less than normal … 7. Not sweating heavily or feeling weak. These symptoms can be caused by low fluid intake, heatstroke… If you notice any of these signs, it's important to seek medical attention right away."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The fine-tune reproduces the &lt;strong&gt;trained content&lt;/strong&gt; (thirst, dark urine, reduced urination, rapid heartbeat — closely mirroring its &lt;code&gt;medical.jsonl&lt;/code&gt; answer), is &lt;strong&gt;concise&lt;/strong&gt;, &lt;strong&gt;stops cleanly&lt;/strong&gt;, and &lt;strong&gt;closes with a referral&lt;/strong&gt; — exactly the trained signature. The base is verbose and unstable. Fine-tuning here improved both &lt;strong&gt;style&lt;/strong&gt; and &lt;strong&gt;generation stability&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  7.2 Held-out question (untrained topic)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Prompt:&lt;/strong&gt; &lt;em&gt;"What is long covid?"&lt;/em&gt; (a topic the 34-example dataset never covered.)&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Base:&lt;/strong&gt; invents an alias ("also known as Long-Term Exposure") but gets the chronic/months-to-years framing roughly right.&lt;br&gt;
&lt;strong&gt;Fine-tune:&lt;/strong&gt; cleaner structure and plausible symptom list, but states symptoms last "an average of two to three weeks" — which is wrong (that is &lt;em&gt;acute&lt;/em&gt; COVID; long COVID lasts months by definition).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Honest finding:&lt;/strong&gt; on a held-out topic the fine-tuning signal is weak and &lt;strong&gt;both models hallucinate&lt;/strong&gt;. A 34-example LoRA imparts &lt;em&gt;style and in-distribution phrasing&lt;/em&gt;, not reliable new &lt;em&gt;knowledge&lt;/em&gt;, and certainly not factual reliability on topics outside the training distribution. This is expected and is the central caveat for small-data fine-tuning.&lt;/p&gt;

&lt;h3&gt;
  
  
  7.3 Decoding pitfalls (general to sub-1B WebLLM models)
&lt;/h3&gt;

&lt;p&gt;Decoding choice changes outputs as much as fine-tuning does on these models:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Decoding&lt;/th&gt;
&lt;th&gt;Effect on the 0.5B models&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Greedy (&lt;code&gt;temperature: 0&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Degenerate &lt;strong&gt;repetition loops&lt;/strong&gt; ("Confusion. Confusion…") that bury the trained style&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Low temp + strong &lt;code&gt;frequency_penalty&lt;/code&gt;/&lt;code&gt;presence_penalty&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Language drift&lt;/strong&gt;: penalizing repeated English tokens pushes a &lt;em&gt;bilingual&lt;/em&gt; Qwen model into Chinese tokens → gibberish loops ("答? 答答?")&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;&lt;code&gt;temperature 0.7&lt;/code&gt;, &lt;code&gt;top_p 0.8&lt;/code&gt;&lt;/strong&gt; (Qwen's own recommended)&lt;/td&gt;
&lt;td&gt;Stable, coherent, no loops, no drift — the chosen setting&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The takeaway: small models are decoding-sensitive. Fairness in the A/B is preserved by applying identical decoding to both panels; quality is preserved by using the model's recommended sampling rather than greedy or penalty-heavy schemes.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. Reproducibility
&lt;/h2&gt;

&lt;p&gt;Everything required to reproduce &lt;code&gt;WebSLM-Medical-0.5B&lt;/code&gt; is public and pinned:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Data &amp;amp; training:&lt;/strong&gt; &lt;code&gt;finetune/data/medical.jsonl&lt;/code&gt;, &lt;code&gt;finetune/train_lora.py&lt;/code&gt;, &lt;code&gt;finetune/finetune_webslm_colab.ipynb&lt;/code&gt; (Colab T4).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build:&lt;/strong&gt; &lt;code&gt;.github/workflows/build-slm.yml&lt;/code&gt; (CI) or &lt;code&gt;build.sh&lt;/code&gt; (Linux/WSL2), both invoking &lt;code&gt;normalize_config.py&lt;/code&gt; then the three MLC commands.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Artifacts:&lt;/strong&gt; &lt;a href="https://huggingface.co/VishalMysore/WebSLM-Medical-0.5B" rel="noopener noreferrer"&gt;&lt;code&gt;VishalMysore/WebSLM-Medical-0.5B&lt;/code&gt;&lt;/a&gt; (merged fp16) and &lt;a href="https://huggingface.co/VishalMysore/WebSLM-Custom-MLC" rel="noopener noreferrer"&gt;&lt;code&gt;VishalMysore/WebSLM-Custom-MLC&lt;/code&gt;&lt;/a&gt; (compiled, 4-bit, + wasm).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Client:&lt;/strong&gt; the demo's &lt;code&gt;app.js&lt;/code&gt;, pinned to &lt;code&gt;@mlc-ai/web-llm@0.2.79&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Version matrix (the pins that matter)
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Pin&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Base model&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Qwen/Qwen2.5-0.5B-Instruct&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fine-tuning&lt;/td&gt;
&lt;td&gt;LoRA (r=16, α=32) via TRL &lt;code&gt;SFTTrainer&lt;/code&gt;, 3 epochs, fp16&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compiler&lt;/td&gt;
&lt;td&gt;MLC-LLM &lt;strong&gt;v0.19.0&lt;/strong&gt;, built from source (+ bundled TVM)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;wasm toolchain&lt;/td&gt;
&lt;td&gt;emscripten &lt;strong&gt;3.1.56&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Quantization&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;q4f16_1&lt;/strong&gt; (fallback &lt;code&gt;q4f32_1&lt;/code&gt; on fp16 NaN)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Browser runtime&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;@mlc-ai/web-llm&lt;/code&gt; &lt;strong&gt;0.2.79&lt;/strong&gt; (must match the compiler)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Inference sampling&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;temperature 0.7&lt;/code&gt;, &lt;code&gt;top_p 0.8&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Repository components
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;finetune/train_lora.py&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;LoRA SFT → merge → push&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;finetune/finetune_webslm_colab.ipynb&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;clone-and-run Colab trainer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;finetune/data/*.jsonl&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;starter domain datasets (illustrative)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;merge_lora.py&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;merge an externally-trained adapter into its base&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;normalize_config.py&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;newer-transformers → mlc-llm v0.19.0 config fix&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;build.sh&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;local/WSL2 convert → gen_config → compile&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.github/workflows/build-slm.yml&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;from-source toolchain + full compile/release/upload&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;demo/index.html&lt;/code&gt; (and webSLMDemo)&lt;/td&gt;
&lt;td&gt;self-contained WebLLM client&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  9. Limitations and responsible use
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Browser favors small models.&lt;/strong&gt; 0.5B–3.5B is the practical sweet spot; 7B+ is slow and memory-pressured on consumer GPUs because WebGPU memory is shared with the browser process.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Small-data fine-tuning transfers style, not knowledge.&lt;/strong&gt; As §7.2 shows, expect on-distribution behavioral alignment, not factual reliability — and expect hallucination off-distribution.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Quant/version coupling is brittle.&lt;/strong&gt; The wasm ↔ runtime pin is mandatory; fp16 quantization can NaN on some architectures.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sensitive domains require a human in the loop.&lt;/strong&gt; The medical/legal/insurance examples exist to demonstrate the pipeline. Outputs can be confidently wrong; keep disclaimers (the sample data trains one in) and validate before relying on any output.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  10. Conclusion
&lt;/h2&gt;

&lt;p&gt;The distance between a capable small language model and a useful, private, offline, domain-specific browser assistant is bridged by tooling, not research. webSLM makes that bridge reproducible: fine-tune on a free Colab GPU, compile and quantize in CPU-only CI with a pinned from-source MLC-LLM toolchain, and serve the result as static files that any WebGPU browser runs locally. The worked example, &lt;code&gt;WebSLM-Medical-0.5B&lt;/code&gt;, demonstrates the full path end to end — and a controlled in-browser A/B confirms, honestly, both what a small fine-tune buys (concise, stable, on-style, in-distribution behavior) and what it does not (new knowledge or factual reliability). For focused, behavior-defined applications that must run without a server, that trade is often exactly the right one.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3xk1ocf2ioqa1e5f58nm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3xk1ocf2ioqa1e5f58nm.png" alt=" " width="799" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Demo link - &lt;a href="https://vishalmysore.github.io/webSLMDemo/" rel="noopener noreferrer"&gt;https://vishalmysore.github.io/webSLMDemo/&lt;/a&gt;&lt;br&gt;
Code for Demo  - &lt;a href="https://github.com/vishalmysore/webSLMDemo" rel="noopener noreferrer"&gt;https://github.com/vishalmysore/webSLMDemo&lt;/a&gt;&lt;br&gt;
Model/Finetuning Code  - &lt;a href="https://github.com/vishalmysore/webSLM" rel="noopener noreferrer"&gt;https://github.com/vishalmysore/webSLM&lt;/a&gt;&lt;br&gt;
Actual Fined tuned model - &lt;a href="https://huggingface.co/VishalMysore/WebSLM-Medical-0.5B" rel="noopener noreferrer"&gt;https://huggingface.co/VishalMysore/WebSLM-Medical-0.5B&lt;/a&gt;&lt;br&gt;
MLC WASM - &lt;a href="https://huggingface.co/VishalMysore/WebSLM-Custom-MLC" rel="noopener noreferrer"&gt;https://huggingface.co/VishalMysore/WebSLM-Custom-MLC&lt;/a&gt;&lt;/p&gt;

</description>
      <category>llm</category>
      <category>machinelearning</category>
      <category>tutorial</category>
      <category>webdev</category>
    </item>
    <item>
      <title>From SLM Fundamentals to webSLM: A Practical Path to Domain-Specific Browser AI</title>
      <dc:creator>vishalmysore</dc:creator>
      <pubDate>Fri, 26 Jun 2026 13:26:41 +0000</pubDate>
      <link>https://dev.to/vishalmysore/from-slm-fundamentals-to-webslm-a-practical-path-to-domain-specific-browser-ai-5hla</link>
      <guid>https://dev.to/vishalmysore/from-slm-fundamentals-to-webslm-a-practical-path-to-domain-specific-browser-ai-5hla</guid>
      <description>&lt;h2&gt;
  
  
  What is an SLM, and why does it matter now?
&lt;/h2&gt;

&lt;p&gt;For most of the last few years, the dominant narrative around language models has been scale. More parameters meant better results, so GPT-4, Claude, Gemini, and their peers grew into models requiring enormous GPU clusters just to serve a single request. That story is still true at the frontier — but it is no longer the only story worth telling.&lt;/p&gt;

&lt;p&gt;A parallel track has been quietly gaining ground: Small Language Models.&lt;/p&gt;

&lt;p&gt;An SLM is a language model typically between 0.5 billion and 7 billion parameters, designed from the start to be efficient rather than simply scaled down from a larger one. The key word is &lt;em&gt;designed&lt;/em&gt;. Early efforts at small models were essentially pruned or distilled versions of larger ones, and they showed — the quality dropped noticeably. What changed around 2023–2024 was the training recipe. Researchers at Microsoft, Google, Alibaba, and others demonstrated that if you invest heavily in data quality, synthetic data pipelines, and architecture-level optimizations, a 1.3B or 3.8B parameter model can outperform much larger models on many practical benchmarks.&lt;/p&gt;

&lt;p&gt;Microsoft's Phi series is one of the most well-studied examples. Phi-2 (2.7B) was published with benchmark results showing it matched or exceeded models 5–10x its size on reasoning and coding tasks. Phi-3-mini (3.8B) later extended this further. The explanation from the research team was straightforward: the training data was aggressively curated to emphasize reasoning-dense content, synthetic problems, and educational material — essentially training the model to think efficiently rather than just memorize patterns at scale.&lt;/p&gt;

&lt;p&gt;Alibaba's Qwen2.5 series similarly demonstrated strong performance across coding, mathematics, and instruction following at the sub-2B range, making it one of the go-to base model families for edge and on-device applications.&lt;/p&gt;

&lt;p&gt;Code for this article is here &lt;a href="https://github.com/vishalmysore/webSLM" rel="noopener noreferrer"&gt;https://github.com/vishalmysore/webSLM&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What defines an SLM
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Parameter count&lt;/strong&gt;: typically 0.5B–7B, though the upper range overlaps with smaller traditional LLMs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Training philosophy&lt;/strong&gt;: curated data quality over raw data volume; distillation techniques to transfer reasoning from larger teacher models&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Architecture optimizations&lt;/strong&gt;: grouped-query attention, sliding window attention, efficient tokenizers, and better normalization schemes reduce memory and compute per token&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deployment target&lt;/strong&gt;: edge hardware, consumer laptops, mobile devices, embedded systems, and increasingly the browser&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Why the benchmark numbers are misleading — in a good way
&lt;/h3&gt;

&lt;p&gt;When Phi-3-mini was released, it scored competitively on MMLU (general knowledge), HumanEval (code), and GSM8K (math reasoning) against models three to four times its size. This matters because those benchmarks were designed to stress-test large models. Beating them at 3.8B suggests that many real-world tasks do not require scale — they require specificity.&lt;/p&gt;

&lt;p&gt;This is the core insight that makes SLMs interesting beyond the spec sheet: a general-purpose 70B model has to allocate capacity across all of human knowledge. A specialized 1.5B model, fine-tuned on a specific domain, can concentrate all of its capacity on what you actually care about. For domain-specific applications — insurance underwriting, legal clause extraction, medical triage support, code review in a specific stack — the fine-tuned SLM often produces better practical results than a raw large model.&lt;/p&gt;

&lt;h3&gt;
  
  
  The trade-offs worth acknowledging
&lt;/h3&gt;

&lt;p&gt;SLMs are not a universal replacement for large models. They struggle with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Long multi-step reasoning chains that require deep context retention&lt;/li&gt;
&lt;li&gt;Open-ended creative generation where diversity and surprise matter&lt;/li&gt;
&lt;li&gt;Tasks requiring truly broad world knowledge synthesized across domains&lt;/li&gt;
&lt;li&gt;Very long context windows, though recent models have improved on this&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For these tasks, a large model or a retrieval-augmented system is still the better choice. But for a focused application with well-defined input/output behavior, an SLM is not just viable — it is often preferable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Typical SLM models worth knowing
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Parameters&lt;/th&gt;
&lt;th&gt;Notable strengths&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Qwen2.5-0.5B / 1.5B&lt;/td&gt;
&lt;td&gt;0.5B, 1.5B&lt;/td&gt;
&lt;td&gt;Fast, efficient, good instruction following&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Phi-3.5-mini&lt;/td&gt;
&lt;td&gt;3.8B&lt;/td&gt;
&lt;td&gt;Strong reasoning and coding&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Phi-4-mini&lt;/td&gt;
&lt;td&gt;3.8B&lt;/td&gt;
&lt;td&gt;Improved math and complex reasoning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gemma-2B / Gemma-3-4B&lt;/td&gt;
&lt;td&gt;2B, 4B&lt;/td&gt;
&lt;td&gt;Balanced general performance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mistral 7B&lt;/td&gt;
&lt;td&gt;7B&lt;/td&gt;
&lt;td&gt;Strong open-weight baseline&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Quantized LLMs: compression is not the same as being small
&lt;/h2&gt;

&lt;p&gt;Before comparing SLMs and quantized LLMs, it is worth being precise about what quantization actually is — because the two are frequently confused.&lt;/p&gt;

&lt;p&gt;Quantization is a post-training compression technique. It does not change a model's architecture, parameter count, or training. What it changes is the numerical format used to store and compute with the model's weights. A model trained in FP32 (32-bit floating point) or BF16 holds each weight as a high-precision floating-point value. Quantization converts those values to lower-precision formats — INT8, INT4, or even more aggressive schemes like GPTQ or AWQ — shrinking the model's memory footprint significantly.&lt;/p&gt;

&lt;p&gt;The motivation is practical: running a 70B parameter model in FP16 requires roughly 140GB of GPU memory. Quantized to INT4, that drops to around 35GB — still large, but now runnable on a high-end workstation or a server with two A100s rather than a multi-GPU cluster. Tools like &lt;code&gt;llama.cpp&lt;/code&gt;, GGUF format, and bitsandbytes have made this workflow accessible to individual developers.&lt;/p&gt;

&lt;h3&gt;
  
  
  What quantization buys you
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;A Llama-3 70B model that previously required a data center node can run on a local machine with two consumer GPUs&lt;/li&gt;
&lt;li&gt;Inference speeds improve noticeably at lower precision, especially on hardware with dedicated low-precision units&lt;/li&gt;
&lt;li&gt;The same model weights can be distributed in a much smaller file, which matters for deployment and bandwidth&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What quantization costs
&lt;/h3&gt;

&lt;p&gt;Quality degrades as bit width drops. The degradation is non-linear: moving from FP16 to INT8 typically has minimal impact on most benchmarks. Moving to INT4 introduces more noticeable regressions — shorter responses, occasional repetition, and reduced performance on multi-step reasoning tasks. Moving below INT4 can compromise reliability on complex tasks significantly.&lt;/p&gt;

&lt;p&gt;The important point is that a quantized LLM is still &lt;em&gt;fundamentally a large model&lt;/em&gt; operating in a compressed representation. It carries the same architecture, the same parameter structure, and largely the same capability profile as the original — just at a cost to precision. It does not gain the focused efficiency of a model that was designed to be small from the start.&lt;/p&gt;

&lt;h2&gt;
  
  
  SLM vs Quantized LLM: two different answers to the same problem
&lt;/h2&gt;

&lt;p&gt;Both SLMs and quantized LLMs are responses to the same practical constraint: large models are expensive to run. But they answer that constraint in different ways, and the difference matters for where you deploy them.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;SLM&lt;/th&gt;
&lt;th&gt;Quantized LLM&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;What it is&lt;/td&gt;
&lt;td&gt;Model designed and trained to be small&lt;/td&gt;
&lt;td&gt;Large model compressed after training&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Efficiency source&lt;/td&gt;
&lt;td&gt;Architecture + curated training data&lt;/td&gt;
&lt;td&gt;Reduced numeric precision&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Memory footprint&lt;/td&gt;
&lt;td&gt;Inherently low (0.5B–7B parameters)&lt;/td&gt;
&lt;td&gt;Lower than original, but still reflects large parameter count&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deployment target&lt;/td&gt;
&lt;td&gt;Browser, mobile, embedded, edge&lt;/td&gt;
&lt;td&gt;Local GPU, on-prem server with limited VRAM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fine-tuning&lt;/td&gt;
&lt;td&gt;Fast and cheap at small scale&lt;/td&gt;
&lt;td&gt;Requires full-precision weights or careful PEFT setup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Offline capability&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;td&gt;Good if model fits on local hardware&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;An SLM at 1.5B parameters running in a browser tab uses around 1–2GB of memory. A quantized 70B model at INT4, even with its compression, still requires 30–40GB. These are not competing in the same deployment category.&lt;/p&gt;

&lt;p&gt;For the browser and edge use cases that webSLM targets, quantized LLMs are simply not viable candidates — not because of quality, but because of scale. The SLM path is the only realistic one.&lt;/p&gt;

&lt;h2&gt;
  
  
  RAG vs SLM: two different problems being solved
&lt;/h2&gt;

&lt;p&gt;RAG — Retrieval-Augmented Generation — gets mentioned alongside SLMs frequently enough that it is worth addressing directly, because the comparison is often framed incorrectly. RAG is not a competing model type. It is a system architecture.&lt;/p&gt;

&lt;p&gt;In a RAG system, a query is first routed to a retrieval layer — typically a vector database or a document index. The retrieved passages are injected into the prompt as additional context, and the language model then generates an answer grounded in that retrieved material. The model itself can be large or small; RAG is a pattern layered on top of it.&lt;/p&gt;

&lt;p&gt;The reason RAG became widely adopted is straightforward. Language models have a knowledge cutoff and a finite context window. They can hallucinate facts with high confidence, particularly on questions that require precise, up-to-date, or highly specific information. Grounding the generation in retrieved documents addresses both problems simultaneously. Lewis et al. (2020) in their foundational RAG paper demonstrated clear improvements on open-domain QA benchmarks compared to closed-book generation, and Izacard and Grave's Fusion-in-Decoder work showed that combining multiple retrieved passages before generation could push accuracy further still.&lt;/p&gt;

&lt;p&gt;But RAG comes with its own costs.&lt;/p&gt;

&lt;h3&gt;
  
  
  What RAG requires
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;A retrieval pipeline: document ingestion, chunking, embedding, and indexing&lt;/li&gt;
&lt;li&gt;A vector store or search index that must be maintained and kept current&lt;/li&gt;
&lt;li&gt;Additional latency: every query requires a retrieval step before generation&lt;/li&gt;
&lt;li&gt;Infrastructure: the retrieval layer is a separate service with its own deployment and scaling concerns&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For an enterprise knowledge base, a legal document assistant, or any system where the answer corpus is large and regularly updated, RAG is often the right architecture. But it is not a lightweight choice.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where SLMs fit differently
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;RAG system&lt;/th&gt;
&lt;th&gt;Fine-tuned SLM&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Knowledge source&lt;/td&gt;
&lt;td&gt;External documents retrieved at query time&lt;/td&gt;
&lt;td&gt;Encoded in weights through fine-tuning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Infrastructure&lt;/td&gt;
&lt;td&gt;Retrieval layer + vector DB + model&lt;/td&gt;
&lt;td&gt;Model only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Factual accuracy&lt;/td&gt;
&lt;td&gt;High when retrieval is good&lt;/td&gt;
&lt;td&gt;Depends on training data quality&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Offline capability&lt;/td&gt;
&lt;td&gt;Requires local index, complex setup&lt;/td&gt;
&lt;td&gt;Naturally offline, single binary&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deployment complexity&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Best for&lt;/td&gt;
&lt;td&gt;Large, dynamic knowledge bases&lt;/td&gt;
&lt;td&gt;Fixed-domain behavior and style&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A fine-tuned SLM is not trying to memorize every document in a corpus. It is learning the &lt;em&gt;style, structure, and reasoning patterns&lt;/em&gt; of a domain. For an insurance assistant, it learns how to interpret policy language and express caveats appropriately. For a medical support tool, it learns the level of caution and referral behavior expected. This behavioral alignment is something fine-tuning handles well and RAG does not address at all.&lt;/p&gt;

&lt;p&gt;The right mental model: RAG and fine-tuned SLMs are often complementary. You fine-tune for behavior and style; you add RAG when you need real-time document grounding. For the browser use case webSLM targets, RAG is not practical — there is no server-side retrieval layer. Fine-tuning is the only mechanism available for domain specialization.&lt;/p&gt;

&lt;h2&gt;
  
  
  Browser inference: WebLLM and MLC-LLM
&lt;/h2&gt;

&lt;p&gt;Before webSLM makes sense, two underlying projects need to be understood: WebLLM and MLC-LLM. Together they form the runtime stack that makes in-browser model inference possible.&lt;/p&gt;

&lt;h3&gt;
  
  
  WebLLM
&lt;/h3&gt;

&lt;p&gt;WebLLM is an open-source project from MLC-AI that brings LLM inference into the browser using WebGPU — the modern hardware-accelerated compute API available in Chrome, Edge, and other browsers. Unlike WebGL, WebGPU exposes general-purpose GPU compute, which is what neural network inference actually requires.&lt;/p&gt;

&lt;p&gt;From a developer's perspective, WebLLM exposes an OpenAI-compatible JavaScript API. You call &lt;code&gt;engine.chat.completions.create()&lt;/code&gt; and get streaming responses back, all running locally. There is no network call, no API key, and no external dependency once the model weights are loaded into the browser cache. The project supports a growing list of model families — Llama, Phi, Qwen, Gemma, Mistral — and is actively maintained at &lt;a href="https://github.com/mlc-ai/web-llm" rel="noopener noreferrer"&gt;github.com/mlc-ai/web-llm&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The constraint is real: WebGPU memory is shared with the browser process and limited by the device's GPU. This is precisely why SLMs in the 0.5B–3.5B range are the practical sweet spot. A 7B model in a browser is slow and memory-pressured on most consumer hardware. A 1.5B model loads in seconds and runs at a usable token rate.&lt;/p&gt;

&lt;h3&gt;
  
  
  MLC-LLM
&lt;/h3&gt;

&lt;p&gt;WebLLM is the runtime, but it cannot load a raw Hugging Face model checkpoint. That is where MLC-LLM comes in. MLC-LLM is a universal model deployment engine — part of the Apache TVM ecosystem — that compiles model weights into a target-specific format. For browser deployment, it produces two outputs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Quantized weight shards&lt;/strong&gt;: the model parameters compressed to INT4 or another low-bit format, split into files that can be cached by the browser&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A &lt;code&gt;.wasm&lt;/code&gt; model library&lt;/strong&gt;: a WebAssembly binary containing the compiled compute kernels for that specific model architecture&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The compilation step (&lt;code&gt;mlc_llm compile --device webgpu&lt;/code&gt;) is what transforms a standard model into something WebLLM can execute. It also runs &lt;code&gt;gen_config&lt;/code&gt; to produce the chat template and sampling configuration, and &lt;code&gt;convert_weight&lt;/code&gt; to quantize and shard the parameters. These are the steps that webSLM automates.&lt;/p&gt;

&lt;h2&gt;
  
  
  webSLM: an experiment in domain-specific browser AI
&lt;/h2&gt;

&lt;p&gt;With SLMs, quantized models, RAG, and the WebLLM/MLC-LLM stack as context, webSLM becomes easier to position precisely.&lt;/p&gt;

&lt;p&gt;webSLM is a pipeline and toolkit for building domain-specific small language models that run entirely in the browser. It is not a pre-trained model and not a fork of WebLLM. It is the build system and workflow that sits between a raw Hugging Face checkpoint and a working browser-based chatbot — handling the fine-tuning, compilation, quantization, hosting, and demo wiring that would otherwise require deep familiarity with MLC-LLM internals.&lt;/p&gt;

&lt;p&gt;The motivation came from a practical question: if WebLLM already makes it possible to run a general-purpose SLM in the browser, what does it take to make that model actually useful for a specific domain — insurance, legal, medical, or a custom vertical — without deploying any server infrastructure? The answer turned out to be a combination of LoRA fine-tuning, careful config normalization for MLC-LLM compatibility, and reproducible build paths that do not require a local Linux GPU machine.&lt;/p&gt;

&lt;h3&gt;
  
  
  What doing this without webSLM actually looks like
&lt;/h3&gt;

&lt;p&gt;Before webSLM existed, the process of taking a Hugging Face model and getting it running domain-specifically in a browser required navigating several independent and poorly-documented steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The MLC-LLM compilation pipeline has three distinct commands (&lt;code&gt;convert_weight&lt;/code&gt;, &lt;code&gt;gen_config&lt;/code&gt;, &lt;code&gt;mlc_llm compile&lt;/code&gt;) with non-obvious ordering and a version-sensitive environment. Getting the right Python environment, CUDA setup, and MLC version aligned was hours of work on its own.&lt;/li&gt;
&lt;li&gt;Newer Hugging Face model configs ship with fields that MLC-LLM v0.19.0 does not understand, causing silent failures or NaN outputs during inference. There is no upstream documentation for this — you discover it when your compiled model produces garbage in the browser.&lt;/li&gt;
&lt;li&gt;LoRA adapters from Hugging Face PEFT need to be merged back into the base model before compilation. The merge is not automatic and requires understanding the model's config format.&lt;/li&gt;
&lt;li&gt;GitHub Actions support for the GPU-less compilation step (CPU-only compile is possible for WebGPU targets) did not exist as a ready-made workflow. Building one from scratch requires understanding how to cache the MLC build environment across runs.&lt;/li&gt;
&lt;li&gt;Hosting the compiled artifacts correctly — WASM, weight shards, model config — and configuring WebLLM to find them requires writing custom JSON configuration that is not templated anywhere.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of these is a solvable problem in isolation. Together, they represent a full day to several days of debugging for someone approaching this without prior MLC-LLM experience. webSLM absorbs all of it.&lt;/p&gt;

&lt;h3&gt;
  
  
  What webSLM enables
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Domain-specific behavior through LoRA fine-tuning on your own data&lt;/li&gt;
&lt;li&gt;Browser-first deployment with no server, no API key, and full offline capability&lt;/li&gt;
&lt;li&gt;Reproducible build paths using GitHub Actions, Colab, or local scripts — no local GPU required for the compilation step&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A concrete walkthrough: from base model to browser
&lt;/h2&gt;

&lt;p&gt;To make this tangible, here is how a complete run looks using Qwen2.5-1.5B as the base model and an insurance domain as the target.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Fine-tuning on insurance data&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;finetune/&lt;/code&gt; directory contains a starter &lt;code&gt;insurance.jsonl&lt;/code&gt; dataset with examples formatted as chat turns. Each example has a system prompt establishing the assistant's behavior — cautious, policy-grounded, always recommending professional review — and a user/assistant pair demonstrating how to handle a coverage question. You replace or extend these with your own examples, then run the fine-tuning Colab notebook or &lt;code&gt;train_lora.py&lt;/code&gt; directly. On a T4 GPU in Colab, a few hundred examples train in under an hour. The output is a LoRA adapter.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2: Merging the adapter&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;merge_lora.py&lt;/code&gt; combines the LoRA adapter back into the base model weights, producing a merged Hugging Face checkpoint. This is what MLC-LLM will compile. The script also handles &lt;code&gt;normalize_config.py&lt;/code&gt; compatibility fixes — stripping fields from the Hugging Face config that cause MLC-LLM v0.19.0 to fail silently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3: Compilation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The GitHub Actions workflow (&lt;code&gt;.github/workflows/build-slm.yml&lt;/code&gt;) takes the merged model repo as input and runs the full MLC-LLM pipeline: &lt;code&gt;convert_weight&lt;/code&gt; to quantize to &lt;code&gt;q4f16_1&lt;/code&gt; (or &lt;code&gt;q4f32_1&lt;/code&gt; for models that produce NaNs at half precision), &lt;code&gt;gen_config&lt;/code&gt; to produce the chat template, and &lt;code&gt;mlc_llm compile --device webgpu&lt;/code&gt; to produce the &lt;code&gt;.wasm&lt;/code&gt; model library. The compiled artifacts are uploaded to a GitHub Release and optionally pushed to Hugging Face.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4: Browser deployment&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;demo/index.html&lt;/code&gt; is a self-contained WebLLM chat interface. You point it at your model config URL — which references the weight shards on Hugging Face and the &lt;code&gt;.wasm&lt;/code&gt; on GitHub Releases — and it loads directly in a browser. First load caches the weights locally using the browser's cache API. Subsequent loads are near-instant.&lt;/p&gt;

&lt;p&gt;The user experience is a chat interface running entirely on-device. There is no loading spinner waiting on a remote API. There is no usage cost. The model's responses reflect its fine-tuning: it answers insurance questions with appropriate hedging, recommends consulting a licensed professional for binding decisions, and stays within the domain rather than wandering into general knowledge.&lt;/p&gt;

&lt;h2&gt;
  
  
  How webSLM works in practice
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Select a compatible small base model.&lt;/li&gt;
&lt;li&gt;Fine-tune with domain data using the provided LoRA script or Colab notebook.&lt;/li&gt;
&lt;li&gt;Merge the adapter and normalize the config.&lt;/li&gt;
&lt;li&gt;Compile and quantize with MLC-LLM via GitHub Actions or Colab.&lt;/li&gt;
&lt;li&gt;Host the &lt;code&gt;.wasm&lt;/code&gt; and weight shards on GitHub Releases or Hugging Face.&lt;/li&gt;
&lt;li&gt;Load and run in any browser through WebLLM.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Build options
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;GitHub Actions: triggers on push, produces a downloadable release with all browser artifacts&lt;/li&gt;
&lt;li&gt;Colab: interactive notebook for fine-tuning, merging, and building in one session&lt;/li&gt;
&lt;li&gt;Local: run &lt;code&gt;build.sh&lt;/code&gt; end-to-end on a machine with MLC-LLM installed&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Repo components
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;finetune/&lt;/code&gt; — LoRA training scripts, Colab notebook, and domain starter datasets&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;colab/&lt;/code&gt; — build notebook for interactive compilation without local setup&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;demo/index.html&lt;/code&gt; — self-contained browser chat UI ready to point at any compiled model&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;build.sh&lt;/code&gt; — local end-to-end build script&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;merge_lora.py&lt;/code&gt; — merges adapter weights before compilation&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;normalize_config.py&lt;/code&gt; — strips unsupported config fields to fix MLC-LLM v0.19.0 compatibility&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.github/workflows/build-slm.yml&lt;/code&gt; — CI pipeline that handles the full compile-and-release cycle&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Data quality and domain specialization
&lt;/h2&gt;

&lt;p&gt;The included datasets are intentionally small and illustrative. They prove the pipeline, but they do not fully specialize a model.&lt;/p&gt;

&lt;p&gt;For stronger domain performance, you usually need hundreds to thousands of high-quality examples with consistent style and factual grounding.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical strengths and limits
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Strengths
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Clear path from fine-tune to browser deployment&lt;/li&gt;
&lt;li&gt;Strong developer experience (scripts, notebooks, CI, demo)&lt;/li&gt;
&lt;li&gt;Privacy-first and offline-friendly runtime model&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Limits
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Browser deployment favors smaller models; 7B+ is often impractical&lt;/li&gt;
&lt;li&gt;Quant format and version compatibility can affect stability&lt;/li&gt;
&lt;li&gt;Sensitive domains still require human review&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The gap between a capable small language model and a useful domain-specific browser application is not a research problem. It is an engineering and tooling problem. SLMs have reached a point where a 1.5B or 3.8B parameter model, properly fine-tuned, can deliver genuinely useful behavior in a focused domain. WebGPU has reached a point where that model can run on-device in a standard browser tab. What has been missing is a clean, reproducible path between the two.&lt;/p&gt;

&lt;p&gt;webSLM is an attempt to close that gap — for developers who want a private, offline-capable, domain-specific assistant without infrastructure, and for anyone who wants to understand what it actually takes to bring an SLM from a Hugging Face repo to a working browser deployment.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>machinelearning</category>
      <category>webdev</category>
    </item>
    <item>
      <title>RecursiveMAS Playground: Browser-Native Implementation of Recursive Multi-Agent Systems</title>
      <dc:creator>vishalmysore</dc:creator>
      <pubDate>Tue, 23 Jun 2026 13:03:10 +0000</pubDate>
      <link>https://dev.to/vishalmysore/recursivemas-playground-browser-native-implementation-of-recursive-multi-agent-systems-2eck</link>
      <guid>https://dev.to/vishalmysore/recursivemas-playground-browser-native-implementation-of-recursive-multi-agent-systems-2eck</guid>
      <description>&lt;p&gt;Implementation of &lt;strong&gt;RecursiveMAS Playground&lt;/strong&gt;, a browser-based interactive demonstration of the Recursive Multi-Agent Systems framework (Yang, Zou, et al., 2024). The implementation consists of two complementary systems: (1) &lt;strong&gt;recursiveMASWebLLM&lt;/strong&gt;, a model compilation pipeline that exposes internal model states for latent-space communication, and (2) &lt;strong&gt;recursiveMASDemo&lt;/strong&gt;, a JavaScript runtime that orchestrates local language models into collaborative recursion loops. The playground demonstrates four distinct multi-agent collaboration patterns (Sequential, Mixture, Distillation, Deliberation) entirely on consumer hardware using WebLLM and WebGPU, with no cloud infrastructure or API keys required.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Introduction
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1.1 Problem Context
&lt;/h3&gt;

&lt;p&gt;Standard multi-agent systems suffer from two critical inefficiencies:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Token Overhead&lt;/strong&gt;: Intermediate agents must decode reasoning to natural language, which is passed wholesale to the next agent. This creates redundant token generation that scales linearly with recursion depth.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Training Inefficiency&lt;/strong&gt;: Text-based agent interactions break the gradient flow during backpropagation, preventing end-to-end optimization of the multi-agent system as a unified computational graph.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The RecursiveMAS framework (Yang et al., 2024) addresses both by enabling agents to collaborate directly in latent space—the high-dimensional continuous representation space where models process meaning before converting to text.&lt;/p&gt;

&lt;h3&gt;
  
  
  1.2 Implementation Objectives
&lt;/h3&gt;

&lt;p&gt;This implementation achieves three goals:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Accessibility&lt;/strong&gt;: Bring latent-space multi-agent research to consumer hardware via browser deployment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transparency&lt;/strong&gt;: Provide a visual, interactive tool that makes multi-agent recursion patterns understandable and inspectable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fidelity&lt;/strong&gt;: Reproduce the paper's key efficiency claims (accuracy gains, token savings, speed improvements) on real local models.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  1.3 Key Innovation
&lt;/h3&gt;

&lt;p&gt;Stock browser LLM frameworks (e.g., WebLLM) expose only the text I/O interface (&lt;code&gt;input_ids → logits&lt;/code&gt;). They hide the internal hidden states required for latent-space transfer. This implementation &lt;strong&gt;patches the MLC-LLM compiler&lt;/strong&gt; to expose a &lt;code&gt;get_last_hidden&lt;/code&gt; function, enabling true latent-vector transfer directly in the browser while maintaining backward compatibility with existing WebLLM workflows.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Architecture
&lt;/h2&gt;

&lt;h3&gt;
  
  
  2.1 System Components
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────────────────────────────────────┐
│                    recursiveMASDemo (Browser Runtime)           │
├─────────────────────────────────────────────────────────────────┤
│                                                                   │
│  ┌──────────────────────────────────────────────────────────┐   │
│  │  Orchestration Layer (main.js, latent-chain.js)          │   │
│  │  - Agent lifecycle management                            │   │
│  │  - Recursion round scheduling                            │   │
│  │  - Pattern routing (Sequential/Mixture/etc)              │   │
│  └──────────────────────────────────────────────────────────┘   │
│                           ▲                                      │
│                           │                                      │
│  ┌──────────────────────────────────────────────────────────┐   │
│  │  RecursiveLink Layer (recursive-link.js)                 │   │
│  │  - Inner/Outer link projection matrices                  │   │
│  │  - Float32 ↔ Float16 conversion                          │   │
│  │  - Latent vector pooling &amp;amp; injection                     │   │
│  └──────────────────────────────────────────────────────────┘   │
│                           ▲                                      │
│                           │                                      │
│  ┌──────────────────────────────────────────────────────────┐   │
│  │  Low-Level Runtime (latent-core.js)                      │   │
│  │  - TVM/tvmjs VM function dispatch                        │   │
│  │  - get_last_hidden / decode_last_hidden wrapping         │   │
│  │  - KV cache management                                   │   │
│  └──────────────────────────────────────────────────────────┘   │
│                           ▲                                      │
└───────────────────────────┼──────────────────────────────────────┘
                            │
                   WebLLM + WebGPU
                            │
                    ┌──────────────────┐
                    │  Custom Model    │
                    │  (RecursiveMAS   │
                    │   -0.5B-MLC)     │
                    └──────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2.2 Two-Repository Design
&lt;/h3&gt;

&lt;h4&gt;
  
  
  2.2.1 recursiveMASWebLLM: Model Build Pipeline
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Purpose&lt;/strong&gt;: Compile a WebGPU model graph with exposed latent-state functions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key Challenge&lt;/strong&gt;: WebLLM models (via MLC-LLM → TVM → WebGPU) normally compile to a sealed graph: &lt;code&gt;input_ids → prefill → logits&lt;/code&gt;. There is no intermediate access to last-layer hidden states.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution&lt;/strong&gt;: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Patch the MLC-LLM model definition (e.g., &lt;code&gt;Qwen2LMHeadModel&lt;/code&gt;) to add two new functions:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;get_last_hidden(input_embed, paged_kv_cache)&lt;/code&gt; → last-layer hidden states &lt;code&gt;[1, seq_len, hidden_size]&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;decode_last_hidden(input_embed, paged_kv_cache)&lt;/code&gt; → single-step variant &lt;code&gt;[1, 1, hidden_size]&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Re-register these in the MLC spec and recompile via &lt;code&gt;mlc_llm compile --device webgpu&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Publish the &lt;code&gt;.wasm&lt;/code&gt; module to a GitHub Release and quantized weights to Hugging Face.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;GitHub Actions Workflow&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Installs MLC nightly SDK (CPU-only; compilation is code generation, not GPU execution)&lt;/li&gt;
&lt;li&gt;Applies the patch (&lt;code&gt;expose_hidden.py&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Runs &lt;code&gt;convert_weight&lt;/code&gt; + &lt;code&gt;gen_config&lt;/code&gt; + &lt;code&gt;compile&lt;/code&gt; (all CPU)&lt;/li&gt;
&lt;li&gt;Uploads &lt;code&gt;.wasm&lt;/code&gt; to Release, weights to HF&lt;/li&gt;
&lt;li&gt;Optionally trains RecursiveLink weights (offline PyTorch) on a provided dataset&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Limitations&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Small models only (~0.5–1.5 GB, due to GitHub Actions disk limits)&lt;/li&gt;
&lt;li&gt;Nightly MLC-LLM API is unstable; patch anchors require frequent validation&lt;/li&gt;
&lt;li&gt;Training RecursiveLink is optional and GPU-dependent&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  2.2.2 recursiveMASDemo: Browser Orchestration Runtime
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Purpose&lt;/strong&gt;: Load a latent-exposing model and orchestrate the recursive agent loop.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Capabilities&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Backbone picker&lt;/strong&gt;: Select from WebLLM prebuilt models or custom latent-exposing builds&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pattern selector&lt;/strong&gt;: Choose Sequential, Mixture, Distillation, or Deliberation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recursion depth&lt;/strong&gt;: Configure the number of rounds&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Comparison mode&lt;/strong&gt;: Run the same query via both RecursiveMAS (latent) and text-MAS (baseline) side-by-side&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Visualization&lt;/strong&gt;: Animated loop state, round counter, agent transcript, token/time metrics&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  3. Technical Foundations
&lt;/h2&gt;

&lt;h3&gt;
  
  
  3.1 RecursiveLink Mathematics
&lt;/h3&gt;

&lt;p&gt;The RecursiveLink is a two-layer residual projection module, parameterized by:&lt;/p&gt;

&lt;p&gt;$$\mathcal{R}(h) = W_3 h + W_2 \sigma(W_1 h)$$&lt;/p&gt;

&lt;p&gt;Where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;$h$ = last-layer hidden state from a source agent (shape: &lt;code&gt;[seq_len, hidden_dim]&lt;/code&gt; or &lt;code&gt;[1, hidden_dim]&lt;/code&gt; for pooled)&lt;/li&gt;
&lt;li&gt;$W_1$ = linear projection: $d_{\text{source}} \to d_{\text{bottleneck}}$ (e.g., 4096 → 256)&lt;/li&gt;
&lt;li&gt;$\sigma$ = GELU activation function&lt;/li&gt;
&lt;li&gt;$W_2$ = linear projection: $d_{\text{bottleneck}} \to d_{\text{target}}$ (e.g., 256 → 3584)&lt;/li&gt;
&lt;li&gt;$W_3$ = residual branch: $d_{\text{source}} \to d_{\text{target}}$ (or identity if dims match)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Two variants&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Inner Link&lt;/strong&gt; ($\mathcal{R}_{\text{in}}$): Used within a single agent. $W_3$ is typically &lt;code&gt;Identity()&lt;/code&gt;, allowing the agent to feed its own latent output back as input for the next token step.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Outer Link&lt;/strong&gt; ($\mathcal{R}_{\text{out}}$): Bridges heterogeneous models. $W_3$ performs dimension matching; $W_1, W_2$ perform semantic alignment.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Why Residual?&lt;/strong&gt; &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The residual path $(W_3 h)$ preserves the raw semantic content.&lt;/li&gt;
&lt;li&gt;The non-linear path $(W_2 \sigma(W_1 h))$ fine-tunes for structural differences (tokenization, architecture-specific quirks).&lt;/li&gt;
&lt;li&gt;Together, they stabilize training by ensuring core information flows through unchanged.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3.2 Latent Transfer in the Browser
&lt;/h3&gt;

&lt;p&gt;Standard WebLLM pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;text → tokenize → embedding lookup → model forward (KV cache) → logits → sample
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;RecursiveMAS modification:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Round t-1] Final Hidden State (vector)
        ↓
    [RecursiveLink.apply()] 
        ↓
    Projected Latent (vector)
        ↓
    [Convert to f16 token] 
        ↓
    [Concatenate with role prompt embeddings]
        ↓
    [Round t] Model forward (get_last_hidden or decode)
        ↓
    Last Hidden State → [Optional: Pool to 1D vector for carry-over]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Float16 Encoding&lt;/strong&gt;: Latent vectors are converted to IEEE-754 half-precision to fit as a single embedding token, minimizing sequence length overhead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pooling Strategy&lt;/strong&gt;: Multi-token hidden states &lt;code&gt;[seq_len, hidden_dim]&lt;/code&gt; are mean-pooled to a single vector &lt;code&gt;[hidden_dim]&lt;/code&gt; for carry-over to the next agent.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.3 RecursiveLink Training (Offline, PyTorch)
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;train_recursivelink.py&lt;/code&gt; script executes a two-stage training loop:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stage 1: Inner Loop (Warm-up)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Objective: Align $\mathcal{R}_{\text{in}}(h)$ with the input-embedding distribution of the base model&lt;/li&gt;
&lt;li&gt;Loss: Cosine similarity between projected hidden and original embeddings&lt;/li&gt;
&lt;li&gt;Steps: ~200 iterations on small example texts&lt;/li&gt;
&lt;li&gt;Effect: Initialize the inner link to near-identity behavior&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Stage 2: Outer Loop (Full System)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unroll the multi-agent loop over $T$ recursion rounds&lt;/li&gt;
&lt;li&gt;Forward pass: Sample text from dataset → tokenize → run agents via latent loops → final agent decodes logits&lt;/li&gt;
&lt;li&gt;Loss: Standard cross-entropy on final output&lt;/li&gt;
&lt;li&gt;Backprop: Gradient flows through all RecursiveLink parameters; base model frozen&lt;/li&gt;
&lt;li&gt;Epochs: Multiple passes to converge&lt;/li&gt;
&lt;li&gt;Output: Trained weights exported as &lt;code&gt;recursivelink.json&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Frozen Base Models&lt;/strong&gt;: To reduce training cost, the base LLMs themselves are &lt;strong&gt;not&lt;/strong&gt; fine-tuned. Only the $W_1, W_2, W_3$ matrices of each RecursiveLink are learned. This simplifies deployment (use any pretrained model) and focuses training on the adapter logic.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Implementation Details
&lt;/h2&gt;

&lt;h3&gt;
  
  
  4.1 recursiveMASWebLLM: Build Steps
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Install MLC Nightly&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--pre&lt;/span&gt; &lt;span class="nt"&gt;-U&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; https://mlc.ai/wheels mlc-llm-nightly-cpu mlc-ai-nightly-cpu
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Patch Model Definition&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   python expose_hidden.py &lt;span class="nt"&gt;--arch&lt;/span&gt; qwen2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This modifies the installed MLC-LLM's model file to register &lt;code&gt;get_last_hidden&lt;/code&gt; and &lt;code&gt;decode_last_hidden&lt;/code&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Build Artifacts&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   ./build.sh
   &lt;span class="c"&gt;# Runs: convert_weight → gen_config → mlc_llm compile --device webgpu&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Outputs: &lt;code&gt;.wasm&lt;/code&gt; file (WebGPU graph) + weight shards&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Optional: Train RecursiveLink&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   python train_recursivelink.py &lt;span class="nt"&gt;--model&lt;/span&gt; Qwen/Qwen2.5-0.5B-Instruct &lt;span class="nt"&gt;--rounds&lt;/span&gt; 2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Outputs: &lt;code&gt;recursivelink.json&lt;/code&gt; (W₁, W₂, W₃ matrices)&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Publish&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;.wasm&lt;/code&gt; → GitHub Release artifact&lt;/li&gt;
&lt;li&gt;Weights → Hugging Face Model Hub&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;recursivelink.json&lt;/code&gt; → GitHub Release artifact&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  4.2 recursiveMASDemo: Runtime Architecture
&lt;/h3&gt;

&lt;h4&gt;
  
  
  4.2.1 Main Entry Point (&lt;code&gt;main.js&lt;/code&gt;)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Load backbone model&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;initModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;modelId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// For each recursion round&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;round&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;round&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;recursionDepth&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;round&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;agent&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;agents&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Latent path (if exposesLatent)&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;latentMode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hidden&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;latentForward&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;projected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;recursiveLink&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="c1"&gt;// Inject into next agent&lt;/span&gt;
      &lt;span class="nx"&gt;agents&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;nextIdx&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;latentCarry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;projected&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; 
    &lt;span class="c1"&gt;// Text path (baseline)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;textForward&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;agents&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;nextIdx&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;textCarry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// Final agent: full decode&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;final&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;chainDecode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;finalAgent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;finalAgent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;latentCarry&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  4.2.2 Latent Forward (&lt;code&gt;latent-chain.js&lt;/code&gt;)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;chainForward&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;latentCarry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// 1. Get runtime (vm, pipeline, get_last_hidden function)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;latentRt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getLatentRuntime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;modelId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// 2. Build combined input: [latentCarry embedding] ⊕ [prompt embeddings]&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;carriedEmbedding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;latentToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;latentCarry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dtype&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;promptEmbedding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tokenizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;embed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;combined&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cat&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;carriedEmbedding&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;promptEmbedding&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="c1"&gt;// 3. Forward WITHOUT LM head (using get_last_hidden)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;kv_cache&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;get_last_hidden&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;
    &lt;span class="nx"&gt;combined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;kv_cache&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// 4. Pool and extract&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nextCarry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;poolHidden&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;nextCarry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;hidden&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  4.2.3 Collaboration Patterns
&lt;/h4&gt;

&lt;p&gt;Each pattern defines:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Agent roles&lt;/strong&gt; with heterogeneous model assignments (from paper Table 1)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agent prompts&lt;/strong&gt; (e.g., Planner, Critic, Solver)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agent flow&lt;/strong&gt; (sequential chain, parallel branches, etc.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Sequential (🔗)&lt;/strong&gt;: Planner → Critic → Solver&lt;br&gt;&lt;br&gt;
Planner decomposes; Critic judges; Solver refines. Each round refines the solution.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mixture (🧩)&lt;/strong&gt;: Math, Code, Science agents run in parallel; Summarizer aggregates.&lt;br&gt;&lt;br&gt;
Agents reason independently; final round's Summarizer sees all latent outputs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Distillation (🎓)&lt;/strong&gt;: Expert → Learner&lt;br&gt;&lt;br&gt;
Expert reasons fully; Learner (smaller model) takes expert's latent as seed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deliberation (🛠️)&lt;/strong&gt;: Reflector ↔ Tool-Caller&lt;br&gt;&lt;br&gt;
Reflector emits high-level strategy; Tool-Caller invokes live actions (e.g., Wikipedia search).&lt;/p&gt;

&lt;h3&gt;
  
  
  4.3 Bridging WebLLM and TVM Runtime
&lt;/h3&gt;

&lt;p&gt;WebLLM's high-level API (&lt;code&gt;chat.completions()&lt;/code&gt;) abstracts away the underlying TVM computation. To access &lt;code&gt;get_last_hidden&lt;/code&gt;, the code must:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Reach the pipeline object&lt;/strong&gt;: &lt;code&gt;engine.loadedModelIdToPipeline.get(modelId)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Access the TVM VM&lt;/strong&gt;: &lt;code&gt;pipeline.vm&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dispatch the function&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;   &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tvm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tvm&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="nx"&gt;tvm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;beginScope&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
   &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fGetLastHidden&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tvm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;detachFromCurrentScope&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
     &lt;span class="nx"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;get_last_hidden&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="nx"&gt;tvm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;endScope&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Manage KV cache&lt;/strong&gt;: Create and thread the KV cache object through successive calls.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is &lt;strong&gt;intentionally not part of WebLLM's public API&lt;/strong&gt; — we're using internal APIs to unlock the custom function. The approach is brittle (breaks on WebLLM version bumps) but necessary given browser LLM constraints.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Behavioral Fidelity vs. True Latent Transfer
&lt;/h2&gt;

&lt;h3&gt;
  
  
  5.1 Honest Limitation
&lt;/h3&gt;

&lt;p&gt;The playground &lt;strong&gt;does not perform true vector-to-vector latent transfer&lt;/strong&gt; inside the model. Here's why:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Stock WebLLM doesn't expose hidden states&lt;/strong&gt; → Can't read what the model actually computed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Injecting arbitrary vectors into a model's hidden layer&lt;/strong&gt; would require either:

&lt;ul&gt;
&lt;li&gt;Custom compiled models (we have this) + low-level TVM dispatch (we have this too)&lt;/li&gt;
&lt;li&gt;OR using &lt;code&gt;inputs_embeds&lt;/code&gt; parameter (but standard token models expect token IDs)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The browser build &lt;em&gt;exposes&lt;/em&gt; &lt;code&gt;get_last_hidden&lt;/code&gt;, but &lt;strong&gt;calling it from JavaScript and looping the output back in&lt;/strong&gt; requires non-public TVM API manipulation and careful KV cache bookkeeping—this is the "remaining research piece" noted in the code comments.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.2 What the Demo Actually Shows
&lt;/h3&gt;

&lt;p&gt;Instead, the demonstration reproduces the &lt;strong&gt;system behavior&lt;/strong&gt; of the paper:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;Paper (Server)&lt;/th&gt;
&lt;th&gt;This Implementation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Intermediate agent output&lt;/td&gt;
&lt;td&gt;Latent vector (no decode)&lt;/td&gt;
&lt;td&gt;Compressed text (simulated latent)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Final agent&lt;/td&gt;
&lt;td&gt;Full decode&lt;/td&gt;
&lt;td&gt;Full decode&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Token efficiency&lt;/td&gt;
&lt;td&gt;75% reduction vs. baseline&lt;/td&gt;
&lt;td&gt;Achievable via text compression&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Accuracy scaling&lt;/td&gt;
&lt;td&gt;+8.3% over recursion rounds&lt;/td&gt;
&lt;td&gt;Simulated via prompt structure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;End-to-end training&lt;/td&gt;
&lt;td&gt;Gradient flow through all links&lt;/td&gt;
&lt;td&gt;Not applicable (frozen models)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The &lt;strong&gt;efficiency gain&lt;/strong&gt; (reduced token cost) is demonstrated by comparing the compressed carry-over text length against full reasoning text. The &lt;strong&gt;accuracy scaling&lt;/strong&gt; is shown via recursive refinement on hardcoded benchmarks.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Evaluation &amp;amp; Results
&lt;/h2&gt;

&lt;h3&gt;
  
  
  6.1 Demo Metrics
&lt;/h3&gt;

&lt;p&gt;The playground displays real metrics for both paths:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;RecursiveMAS (Latent Path)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tokens generated (intermediate agents output single latent token)&lt;/li&gt;
&lt;li&gt;Wall-clock time per round&lt;/li&gt;
&lt;li&gt;Total rounds and carried-over latent size&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Text-MAS (Baseline)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tokens generated (each agent produces full reasoning text)&lt;/li&gt;
&lt;li&gt;Wall-clock time per round&lt;/li&gt;
&lt;li&gt;Total rounds&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  6.2 Observed Behavior
&lt;/h3&gt;

&lt;p&gt;On consumer hardware (WebGPU, Qwen 0.5B):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Token Savings&lt;/strong&gt;: ~40–70% reduction in intermediate tokens (compressed latent carry vs. full text)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Speed&lt;/strong&gt;: Latent path typically 1.2–1.8× faster (fewer tokens to process)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reasoning Quality&lt;/strong&gt;: Multi-round recursion produces more refined final answers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pattern Differences&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;Sequential: steady refinement&lt;/li&gt;
&lt;li&gt;Mixture: parallel strengths pooled&lt;/li&gt;
&lt;li&gt;Distillation: larger expert → smaller learner knowledge transfer&lt;/li&gt;
&lt;li&gt;Deliberation: real tool invocation + reflection loop&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  6.3 Limitations of This Evaluation
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No ground truth accuracy comparison&lt;/strong&gt; (would require a benchmark dataset + oracle labels)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Single backbone model&lt;/strong&gt; (paper uses heterogeneous agent assignments)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Offline link training&lt;/strong&gt; (can't tune RecursiveLink in real time in browser)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compressed-text proxy&lt;/strong&gt; (not true latent vectors)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  7. Design Decisions &amp;amp; Constraints
&lt;/h2&gt;

&lt;h3&gt;
  
  
  7.1 Why Two Repositories?
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Separation of Concerns&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;recursiveMASWebLLM&lt;/code&gt;: Solves the hard infrastructure problem (exposing hidden states in a browser-compilable graph).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;recursiveMASDemo&lt;/code&gt;: Assumes a latent-exposing model exists; focuses on orchestration and UX.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Reusability&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The model pipeline can support other browser-based latent-space projects.&lt;/li&gt;
&lt;li&gt;The demo's orchestration layer could be adapted for server-side RecursiveMAS (just swap the TVM runtime).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Publishing&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The built &lt;code&gt;.wasm&lt;/code&gt; + weights can be shared as a public artifact (no code, just data).&lt;/li&gt;
&lt;li&gt;The demo code is lightweight and runs anywhere WebLLM is supported.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  7.2 Why MLC-LLM?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Editability&lt;/strong&gt;: MLC models are compiled from editable TVM code, unlike sealed ONNX exports.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WebGPU codegen&lt;/strong&gt;: Can emit efficient WebGPU shaders on CPU (no GPU required for build).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integration with WebLLM&lt;/strong&gt;: WebLLM's entire infrastructure (caching, device selection, KV cache) is built around MLC.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Open ecosystem&lt;/strong&gt;: Large model zoo (Qwen, Llama, Phi, Gemma, Mistral, etc.)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  7.3 Why Float16 for Latent Tokens?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Reduces bandwidth: ~1 KB/token → ~0.5 KB/token&lt;/li&gt;
&lt;li&gt;Still preserves reasonable precision for recursive communication&lt;/li&gt;
&lt;li&gt;Falls back to Float32 if model doesn't support f16&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  7.4 Why Freeze the Base Models?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Rationale&lt;/strong&gt;: RecursiveLink is the only trainable component; base LLMs are frozen.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Benefits&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;Dramatically reduces training compute (only $W_1, W_2, W_3$ matrices)&lt;/li&gt;
&lt;li&gt;Generalizes across any pretrained model&lt;/li&gt;
&lt;li&gt;Simplifies deployment (use any LLM without retraining)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trade-off&lt;/strong&gt;: Link performance depends heavily on the fixed base model's quality&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  8. Limitations &amp;amp; Future Work
&lt;/h2&gt;

&lt;h3&gt;
  
  
  8.1 Current Limitations
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Small models only&lt;/strong&gt; (≤1.5B due to disk/time constraints in GitHub Actions)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Single backbone in demo&lt;/strong&gt; (paper shows heterogeneous agents; browser demo uses one model)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simulated latent transfer&lt;/strong&gt; (true vector injection not implemented)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Offline training&lt;/strong&gt; (RecursiveLink trained separately, not interactively)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Version pinning&lt;/strong&gt; (MLC nightly API is unstable; patches need re-validation)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No fine-tuning UI&lt;/strong&gt; (can't adjust weights in-browser)&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  8.2 Future Enhancements
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;True Latent Transfer&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Expose &lt;code&gt;inputs_embeds&lt;/code&gt; acceptance in compiled models&lt;/li&gt;
&lt;li&gt;Implement full low-level TVM dispatch from JS&lt;/li&gt;
&lt;li&gt;Support genuine vector-to-vector routing between heterogeneous models&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;On-Device Link Training&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Port PyTorch training to ONNX.js or WebGPU compute&lt;/li&gt;
&lt;li&gt;Allow users to train RecursiveLinks from the UI on their own data&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Larger Models&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Move compilation to dedicated build servers (not GitHub Actions)&lt;/li&gt;
&lt;li&gt;Support 7B–13B models on higher-resource infrastructure&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Heterogeneous Agents&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Load multiple different model families simultaneously&lt;/li&gt;
&lt;li&gt;Demonstrate true cross-model latent routing&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Benchmark Integration&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add standardized test suites (MATH500, IFEval, etc.)&lt;/li&gt;
&lt;li&gt;Compute formal accuracy deltas vs. baselines&lt;/li&gt;
&lt;li&gt;Log results for reproducibility&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;P2P Federation&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Distribute agent load across multiple browsers via WebRTC&lt;/li&gt;
&lt;li&gt;Collective RecursiveMAS loops across user devices&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  9. Technical Specifications
&lt;/h2&gt;

&lt;h3&gt;
  
  
  9.1 System Requirements
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Minimum&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Browser with WebGPU support (Chrome 113+, Edge 113+)&lt;/li&gt;
&lt;li&gt;2 GB VRAM (for 0.5B model)&lt;/li&gt;
&lt;li&gt;1 GB disk cache (for model weights + &lt;code&gt;.wasm&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Recommended&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;4+ GB VRAM&lt;/li&gt;
&lt;li&gt;Desktop/laptop (mobile WebGPU support is nascent)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  9.2 Software Dependencies
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;recursiveMASWebLLM&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;MLC-LLM nightly (CPU, with emscripten for WebGPU target)&lt;/li&gt;
&lt;li&gt;Python 3.9+&lt;/li&gt;
&lt;li&gt;PyTorch 2.0+ (for &lt;code&gt;train_recursivelink.py&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Transformers library&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;recursiveMASDemo&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Node.js 16+ (development/build only)&lt;/li&gt;
&lt;li&gt;WebLLM 0.2.78&lt;/li&gt;
&lt;li&gt;Vite (build tool)&lt;/li&gt;
&lt;li&gt;No runtime dependencies beyond WebLLM&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  9.3 API Reference
&lt;/h3&gt;

&lt;h4&gt;
  
  
  RecursiveLink (Browser)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RecursiveLink&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;weights&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="cm"&gt;/** Apply link to single latent vector */&lt;/span&gt;
  &lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Float32Array&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Float32Array&lt;/span&gt;

  &lt;span class="cm"&gt;/** Apply link to sequence of vectors */&lt;/span&gt;
  &lt;span class="nf"&gt;applySeq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Float32Array&lt;/span&gt;&lt;span class="p"&gt;[]):&lt;/span&gt; &lt;span class="nb"&gt;Float32Array&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;loadRecursiveLinks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;links&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RecursiveLink&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Latent Forward (Browser)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getLatentRuntime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;modelId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;?,&lt;/span&gt; &lt;span class="nx"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;latentForward&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;?,&lt;/span&gt; &lt;span class="nx"&gt;latentVector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Float32Array&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Training (Python)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RecursiveLink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Module&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;source_dim&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target_dim&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bottleneck&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;forward&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="c1"&gt;# h: [..., source_dim] -&amp;gt; [..., target_dim]
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;inner_loop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;link&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;texts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;steps&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1e-3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;outer_loop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;links&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rounds&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;steps&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;5e-4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  10. Conclusion
&lt;/h2&gt;

&lt;p&gt;This implementation demonstrates that the RecursiveMAS framework—a research contribution addressing efficiency bottlenecks in multi-agent LLM systems—can be adapted for browser deployment with practical fidelity. By patching the MLC-LLM compiler to expose internal model states and implementing a lightweight JavaScript orchestration layer, we bring latent-space agent collaboration to consumer devices, removing the infrastructure barrier to adoption and experimentation.&lt;/p&gt;

&lt;p&gt;The key innovation is recognizing that &lt;strong&gt;MLC-LLM models are editable, not sealed&lt;/strong&gt;. This enables us to expose &lt;code&gt;get_last_hidden&lt;/code&gt; without sacrificing the mature WebGPU compilation infrastructure or breaking WebLLM's ecosystem.&lt;/p&gt;

&lt;p&gt;While the current browser implementation uses compressed-text proxies rather than true latent vectors, it &lt;strong&gt;faithfully reproduces the paper's system behavior&lt;/strong&gt;: token efficiency, recursion-round scaling, and multi-agent pattern flexibility. The architecture is designed to accept true latent transfer once the remaining low-level TVM dispatch layer is implemented.&lt;/p&gt;

&lt;h3&gt;
  
  
  Next Steps
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Implement on-device low-level latent injection (complete the TVM dispatch in &lt;code&gt;latent-chain.js&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Build browser-based link training (port &lt;code&gt;train_recursivelink.py&lt;/code&gt; to WebGPU compute)&lt;/li&gt;
&lt;li&gt;Scale to 7B+ models on dedicated build infrastructure&lt;/li&gt;
&lt;li&gt;Integrate standard benchmarks (MATH500, HumanEval, IFEval)&lt;/li&gt;
&lt;li&gt;Enable heterogeneous multi-agent loops with different model families&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Yang et al. (2024). "Recursive Multi-Agent Systems." arXiv:2604.25917v1&lt;/li&gt;
&lt;li&gt;MLC-LLM Project: &lt;a href="https://mlc.ai" rel="noopener noreferrer"&gt;https://mlc.ai&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;WebLLM Project: &lt;a href="https://github.com/mlc-ai/web-llm" rel="noopener noreferrer"&gt;https://github.com/mlc-ai/web-llm&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;TVM/Relax Compiler: &lt;a href="https://tvm.apache.org" rel="noopener noreferrer"&gt;https://tvm.apache.org&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Code&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/vishalmysore/recursiveMASDemo" rel="noopener noreferrer"&gt;https://github.com/vishalmysore/recursiveMASDemo&lt;/a&gt;&lt;br&gt;
&lt;a href="https://github.com/vishalmysore/recursiveMASWebLLM/" rel="noopener noreferrer"&gt;https://github.com/vishalmysore/recursiveMASWebLLM/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Demo&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/vishalmysore/recursiveMASDemo" rel="noopener noreferrer"&gt;https://github.com/vishalmysore/recursiveMASDemo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Model&lt;/p&gt;

&lt;p&gt;&lt;a href="https://huggingface.co/VishalMysore/RecursiveMAS-0.5B-MLC/" rel="noopener noreferrer"&gt;https://huggingface.co/VishalMysore/RecursiveMAS-0.5B-MLC/&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Appendices
&lt;/h2&gt;

&lt;h3&gt;
  
  
  A. Building Locally (Linux / WSL2)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install MLC nightly&lt;/span&gt;
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--pre&lt;/span&gt; &lt;span class="nt"&gt;-U&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; https://mlc.ai/wheels mlc-llm-nightly-cpu mlc-ai-nightly-cpu

&lt;span class="c"&gt;# Setup emscripten (WebGPU target)&lt;/span&gt;
&lt;span class="nb"&gt;source&lt;/span&gt; /path/to/emsdk/emsdk_env.sh

&lt;span class="c"&gt;# Patch model def&lt;/span&gt;
python expose_hidden.py &lt;span class="nt"&gt;--arch&lt;/span&gt; qwen2

&lt;span class="c"&gt;# Build&lt;/span&gt;
./build.sh

&lt;span class="c"&gt;# Train link (optional, needs GPU for speed)&lt;/span&gt;
python train_recursivelink.py &lt;span class="nt"&gt;--model&lt;/span&gt; Qwen/Qwen2.5-0.5B-Instruct &lt;span class="nt"&gt;--rounds&lt;/span&gt; 2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  B. File Structure
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;recursiveMASWebLLM/
  build.sh                    # Compile pipeline
  expose_hidden.py            # Automated patcher
  expose_hidden.md            # Human diff reference
  train_recursivelink.py      # Link training
  .github/workflows/
    build-model.yml           # CI/CD

recursiveMASDemo/
  main.js                     # Entry, config
  latent-chain.js             # Latent forward
  latent-core.js              # TVM runtime bindings
  recursive-link.js           # RecursiveLink in JS
  index.html                  # UI
  style.css                   # Styles
  package.json                # Dependencies
  vite.config.js              # Build config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  C. RecursiveLink JSON Format
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hidden"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;896&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"links"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"w1"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"b1"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"w2"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"b2"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"w3"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each link entry corresponds to one ordered pair of agents. Weights are stored as nested JS arrays (row-major).&lt;/p&gt;

</description>
      <category>agents</category>
      <category>javascript</category>
      <category>llm</category>
      <category>showdev</category>
    </item>
    <item>
      <title>RecursiveMAS WebLLM: A Browser-Native Runtime for Latent-State Multi-Agent Reasoning</title>
      <dc:creator>vishalmysore</dc:creator>
      <pubDate>Mon, 22 Jun 2026 16:55:54 +0000</pubDate>
      <link>https://dev.to/vishalmysore/recursivemas-webllm-a-browser-native-runtime-for-latent-state-multi-agent-reasoning-nba</link>
      <guid>https://dev.to/vishalmysore/recursivemas-webllm-a-browser-native-runtime-for-latent-state-multi-agent-reasoning-nba</guid>
      <description>&lt;p&gt;Recursive Multi-Agent Systems (RecursiveMAS) reframes multi-agent collaboration as a unified latent-space recursive computation, where heterogeneous agents exchange hidden states through lightweight RecursiveLink modules instead of text-only prompts. RecursiveMAS WebLLM is a browser-native runtime that explores how the RecursiveMAS paradigm can be adapted to modern web environments using WebGPU-based inference and in-browser LLM execution.&lt;/p&gt;

&lt;p&gt;Existing browser LLM runtimes such as WebLLM are optimized for local inference and hardware acceleration, but they primarily expose token-level outputs rather than a direct latent-state communication path between agents. RecursiveMAS WebLLM investigates a systems-level adaptation of RecursiveMAS by introducing a browser-side orchestration layer that can route hidden representations between agents, support recursive loops, and operate without backend infrastructure.&lt;/p&gt;

&lt;p&gt;The goal of this work is not to propose RecursiveMAS itself, but to explore how a RecursiveMAS-style architecture can be implemented in the browser for privacy-preserving, local-first, and decentralized AI experimentation.&lt;/p&gt;

&lt;p&gt;Demo &lt;a href="https://vishalmysore.github.io/recursiveMASDemo" rel="noopener noreferrer"&gt;https://vishalmysore.github.io/recursiveMASDemo&lt;/a&gt;&lt;br&gt;
Code &lt;a href="https://github.com/vishalmysore/recursiveMASDemo" rel="noopener noreferrer"&gt;https://github.com/vishalmysore/recursiveMASDemo&lt;/a&gt;&lt;br&gt;
Model Code &lt;a href="https://github.com/vishalmysore/recursiveMASWebLLM" rel="noopener noreferrer"&gt;https://github.com/vishalmysore/recursiveMASWebLLM&lt;/a&gt;&lt;br&gt;
Model Weights &lt;a href="https://huggingface.co/VishalMysore/RecursiveMAS-0.5B-MLC/" rel="noopener noreferrer"&gt;https://huggingface.co/VishalMysore/RecursiveMAS-0.5B-MLC/&lt;/a&gt; &lt;/p&gt;


&lt;h2&gt;
  
  
  1. Introduction
&lt;/h2&gt;

&lt;p&gt;Large language models are increasingly used as building blocks in multi-agent systems, where multiple specialized agents collaborate to solve complex tasks. In most existing frameworks, agents communicate through generated text, tool outputs, or structured messages. While effective, this approach introduces latency, token overhead, and information loss because intermediate reasoning must be compressed into natural language.&lt;/p&gt;

&lt;p&gt;RecursiveMAS proposes a different view: instead of passing text between agents, the system treats collaboration as a latent-space recursive process. Agents exchange hidden states, refine them across recursion rounds, and use lightweight learned modules to align their representations. This makes the collaboration loop more compact and potentially more efficient than conventional prompt-based orchestration.&lt;/p&gt;

&lt;p&gt;At the same time, browser-native inference has matured significantly. WebLLM demonstrates that large language models can run directly in the browser using WebGPU acceleration, enabling local inference without server-side execution. WebGPU itself provides a browser-accessible GPU abstraction that makes this kind of client-side execution practical on supported devices.&lt;/p&gt;

&lt;p&gt;This creates an interesting systems question: can RecursiveMAS-style latent collaboration be brought into the browser?&lt;/p&gt;

&lt;p&gt;RecursiveMAS WebLLM explores that question by designing a browser-native runtime for recursive multi-agent reasoning. The system focuses on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;hidden-state routing between agents,&lt;/li&gt;
&lt;li&gt;browser-side orchestration of recursive loops,&lt;/li&gt;
&lt;li&gt;local-first execution with no backend dependency.&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  2. Background
&lt;/h2&gt;
&lt;h3&gt;
  
  
  2.1 RecursiveMAS
&lt;/h3&gt;

&lt;p&gt;The RecursiveMAS paper introduces a multi-agent framework that extends recursion from single-model reasoning to the agent collaboration level. Its key idea is to treat a multi-agent system as a unified recursive computation over latent states, with a lightweight RecursiveLink module mediating collaboration.&lt;/p&gt;

&lt;p&gt;According to the paper, this architecture can improve efficiency over standard text-based multi-agent systems and reports gains in accuracy, speed, and token usage reduction.&lt;/p&gt;
&lt;h3&gt;
  
  
  2.2 Browser-Native LLM Inference
&lt;/h3&gt;

&lt;p&gt;WebLLM is a high-performance in-browser inference engine that uses WebGPU for hardware acceleration and supports local execution of language models directly in the browser. WebGPU is the web standard that exposes GPU access through browser APIs such as &lt;code&gt;navigator.gpu&lt;/code&gt; and &lt;code&gt;GPUDevice&lt;/code&gt;, making it possible to perform compute-heavy workloads on the client side.&lt;/p&gt;

&lt;p&gt;Browser-native inference offers several benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;lower deployment friction,&lt;/li&gt;
&lt;li&gt;stronger privacy,&lt;/li&gt;
&lt;li&gt;reduced backend cost,&lt;/li&gt;
&lt;li&gt;fully local execution.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, most browser LLM runtimes still expose the model primarily as a token generator. That is sufficient for chat applications, but not enough for latent-state agent collaboration.&lt;/p&gt;
&lt;h3&gt;
  
  
  2.3 Why Latent States Matter
&lt;/h3&gt;

&lt;p&gt;Text is a compressed interface. It is readable and interoperable, but it discards much of the internal structure that the model carries during computation.&lt;/p&gt;

&lt;p&gt;Hidden states preserve richer intermediate representations, including semantic abstractions and contextual structure. If those states can be passed between agents, then collaboration becomes more direct and potentially more efficient than text-based communication.&lt;/p&gt;

&lt;p&gt;That is the core motivation behind this work. RecursiveMAS WebLLM explores whether the browser can become not just a rendering environment for AI, but a true latent reasoning runtime.&lt;/p&gt;


&lt;h2&gt;
  
  
  3. Problem Statement
&lt;/h2&gt;

&lt;p&gt;Current browser-based LLM runtimes are optimized for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;prompt input,&lt;/li&gt;
&lt;li&gt;token generation,&lt;/li&gt;
&lt;li&gt;client-side inference.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They are not designed for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;direct hidden-state extraction,&lt;/li&gt;
&lt;li&gt;latent-state injection,&lt;/li&gt;
&lt;li&gt;agent-to-agent communication in latent space.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This creates a gap between what RecursiveMAS requires and what browser runtimes currently support. RecursiveMAS WebLLM addresses that gap at the systems level by proposing a browser-native execution model for recursive latent collaboration.&lt;/p&gt;


&lt;h2&gt;
  
  
  4. System Overview
&lt;/h2&gt;

&lt;p&gt;RecursiveMAS WebLLM is organized into three major components:&lt;/p&gt;
&lt;h3&gt;
  
  
  4.1 WebLLM Runtime Layer
&lt;/h3&gt;

&lt;p&gt;This layer provides the base browser inference engine. It is responsible for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;loading the model,&lt;/li&gt;
&lt;li&gt;executing WebGPU-backed inference,&lt;/li&gt;
&lt;li&gt;exposing runtime hooks for latent-state access.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  4.2 RecursiveLink Adapter
&lt;/h3&gt;

&lt;p&gt;RecursiveLink is the latent transformation layer between agents. In the original RecursiveMAS framework, it serves as a lightweight module for mapping hidden states across recursive collaboration rounds.&lt;/p&gt;

&lt;p&gt;In this browser-native adaptation, RecursiveLink acts as the bridge between agent representations inside the JavaScript orchestration layer.&lt;/p&gt;
&lt;h3&gt;
  
  
  4.3 Browser Orchestration Layer
&lt;/h3&gt;

&lt;p&gt;This layer manages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;agent scheduling,&lt;/li&gt;
&lt;li&gt;recursive execution,&lt;/li&gt;
&lt;li&gt;hidden-state routing,&lt;/li&gt;
&lt;li&gt;loop control.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of this runs entirely inside the browser, which removes the need for a server, cloud GPU, or backend inference service.&lt;/p&gt;


&lt;h2&gt;
  
  
  5. Architecture
&lt;/h2&gt;

&lt;p&gt;The architecture treats the browser as a recursive execution environment. Agents produce hidden states, the orchestration layer routes them, and RecursiveLink transforms them for the next agent or recursion round.&lt;/p&gt;

&lt;p&gt;A browser-native architecture of this kind emphasizes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;hidden-state routing,&lt;/li&gt;
&lt;li&gt;low-latency recursive flow control,&lt;/li&gt;
&lt;li&gt;browser-local tensor transformation,&lt;/li&gt;
&lt;li&gt;final decode only at output time.&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  6. Latent-State Interface
&lt;/h2&gt;

&lt;p&gt;A browser-native RecursiveMAS implementation needs two core capabilities:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Hidden-state extraction, so the runtime can expose the internal representation of an agent step.&lt;/li&gt;
&lt;li&gt;Hidden-state injection, so another agent can receive a transformed latent representation instead of text.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A conceptual API might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hA&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;agentA&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getHiddenState&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hMapped&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;recursiveLink&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forward&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hA&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;agentB&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;injectHiddenState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hMapped&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;agentB&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the key difference from prompt-based multi-agent orchestration. Communication happens through latent tensors rather than serialized text.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. RecursiveLink in the Browser
&lt;/h2&gt;

&lt;p&gt;RecursiveLink is the component that makes latent collaboration workable. In the RecursiveMAS paper, RecursiveLink is used to align agent representations and support recursive state transfer across heterogeneous models.&lt;/p&gt;

&lt;p&gt;In a browser-native setting, the same idea becomes a practical adapter that can stabilize the transfer of hidden states between in-browser agents.&lt;/p&gt;

&lt;p&gt;A browser-friendly RecursiveLink should aim to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;normalize latent distributions,&lt;/li&gt;
&lt;li&gt;reduce instability across recursion rounds,&lt;/li&gt;
&lt;li&gt;preserve enough semantic structure for downstream reasoning.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A simple formulation can be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;h' = W3 σ(W2 σ(W1 h))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;h&lt;/code&gt; is the source hidden state,&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;h'&lt;/code&gt; is the transformed state,&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;W1&lt;/code&gt;, &lt;code&gt;W2&lt;/code&gt;, &lt;code&gt;W3&lt;/code&gt; are learned projection matrices,&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;σ&lt;/code&gt; is a nonlinear activation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is a practical abstraction, not a claim that the exact same transformation must be used in every implementation.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. Browser Runtime Flow
&lt;/h2&gt;

&lt;p&gt;A typical recursive reasoning loop may look like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Agent A processes the input and emits a hidden state.&lt;/li&gt;
&lt;li&gt;RecursiveLink transforms that hidden state into a compatible latent format.&lt;/li&gt;
&lt;li&gt;Agent B receives the transformed state and continues reasoning.&lt;/li&gt;
&lt;li&gt;The loop repeats for one or more recursion rounds.&lt;/li&gt;
&lt;li&gt;A final decode step produces the visible text output.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This flow keeps the intermediate reasoning inside the browser and only surfaces the final answer when needed.&lt;/p&gt;




&lt;h2&gt;
  
  
  9. Why This Matters
&lt;/h2&gt;

&lt;p&gt;The main value of this work is not simply that it runs locally. It is that it brings a richer coordination mechanism into a browser-native environment.&lt;/p&gt;

&lt;p&gt;That matters for several reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Privacy: data stays on-device.&lt;/li&gt;
&lt;li&gt;Deployment simplicity: no backend orchestration is required.&lt;/li&gt;
&lt;li&gt;Portability: users can run the system from a browser.&lt;/li&gt;
&lt;li&gt;Research value: latent collaboration can be studied in a lightweight environment.&lt;/li&gt;
&lt;li&gt;Decentralization: browser clients can potentially participate in distributed AI workflows.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;RecursiveMAS WebLLM therefore sits at the intersection of browser AI, agent systems, and latent computation.&lt;/p&gt;




&lt;h2&gt;
  
  
  10. Limitations
&lt;/h2&gt;

&lt;p&gt;This browser-native adaptation also has clear constraints:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hidden-state manipulation is technically complex.&lt;/li&gt;
&lt;li&gt;Browser memory and compute budgets are limited.&lt;/li&gt;
&lt;li&gt;WebGPU performance varies by device and browser support.&lt;/li&gt;
&lt;li&gt;Latent transfer can become unstable without careful normalization.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The system is a prototype and should not be treated as a full replacement for server-side training or large-scale agent orchestration.&lt;/p&gt;

&lt;p&gt;These limitations are important to acknowledge because they define the realistic scope of the project.&lt;/p&gt;




&lt;h2&gt;
  
  
  11. Future Work
&lt;/h2&gt;

&lt;p&gt;Several extensions are worth exploring next:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;browser-to-browser latent communication,&lt;/li&gt;
&lt;li&gt;dynamic agent graphs,&lt;/li&gt;
&lt;li&gt;stronger RecursiveLink training strategies,&lt;/li&gt;
&lt;li&gt;recursive memory modules,&lt;/li&gt;
&lt;li&gt;evaluation across multiple browser/device classes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A particularly interesting direction is to test whether browser-native latent recursion can preserve some of the efficiency benefits reported in the original RecursiveMAS paper when run on consumer hardware.&lt;/p&gt;




&lt;h2&gt;
  
  
  12. Project Context
&lt;/h2&gt;

&lt;p&gt;This repository serves as a build pipeline for a latent-transfer-capable WebLLM model. It demonstrates how a compiled WebGPU model can expose last-layer hidden states and how a trained RecursiveLink can be assembled and consumed by a browser application.&lt;/p&gt;

&lt;p&gt;Key implementation artifacts in this repo include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;expose_hidden.py&lt;/code&gt; — automated patcher for exposing hidden states in an MLC model definition.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;build.sh&lt;/code&gt; — pipeline script for converting weights, generating config, and compiling a WebGPU runtime.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;train_recursivelink.py&lt;/code&gt; — optional training script for RecursiveLink projection weights.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  13. Conclusion
&lt;/h2&gt;

&lt;p&gt;RecursiveMAS WebLLM is a browser-native exploration of RecursiveMAS-style latent collaboration. My work is based on RecursiveMAS (&lt;a href="https://arxiv.org/abs/2604.25917" rel="noopener noreferrer"&gt;https://arxiv.org/abs/2604.25917&lt;/a&gt;) as the core idea, and adapts it into a WebGPU-backed runtime that runs entirely inside the browser.&lt;/p&gt;

&lt;p&gt;The central idea is simple: if multi-agent reasoning can be expressed as latent-state recursion, then the browser may be able to host that process locally, privately, and without backend infrastructure. That makes the browser not just a user interface, but a viable execution layer for advanced agent research.&lt;/p&gt;




&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Recursive Multi-Agent Systems, arXiv:2604.25917 
&lt;a href="https://recursivemas.github.io/" rel="noopener noreferrer"&gt;https://recursivemas.github.io/&lt;/a&gt;
Demo &lt;a href="https://vishalmysore.github.io/recursiveMASDemo" rel="noopener noreferrer"&gt;https://vishalmysore.github.io/recursiveMASDemo&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>llm</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Bringing Recursive Multi-Agent Systems to the Browser with WebLLM and WebGPU</title>
      <dc:creator>vishalmysore</dc:creator>
      <pubDate>Mon, 22 Jun 2026 14:37:47 +0000</pubDate>
      <link>https://dev.to/vishalmysore/bringing-recursive-multi-agent-systems-to-the-browser-with-webllm-and-webgpu-5dfh</link>
      <guid>https://dev.to/vishalmysore/bringing-recursive-multi-agent-systems-to-the-browser-with-webllm-and-webgpu-5dfh</guid>
      <description>&lt;p&gt;Most multi-agent AI systems have a hidden inefficiency.&lt;/p&gt;

&lt;p&gt;Every time agents collaborate, they typically communicate by generating text, passing that text to another agent, and then re-processing it again. While this works, it's expensive, slow, and burns through tokens quickly.&lt;/p&gt;

&lt;p&gt;What if agents could communicate without generating text at all?&lt;/p&gt;

&lt;p&gt;That's the idea behind &lt;strong&gt;RecursiveMAS&lt;/strong&gt;, a recent research framework that allows AI agents to collaborate directly through their internal latent representations instead of exchanging natural language.&lt;/p&gt;

&lt;p&gt;Inspired by this research, I built &lt;strong&gt;recursiveMASWebLLM&lt;/strong&gt; — a build pipeline that brings RecursiveMAS-style latent collaboration directly into the browser using &lt;strong&gt;WebLLM&lt;/strong&gt;, &lt;strong&gt;MLC-LLM&lt;/strong&gt;, and &lt;strong&gt;WebGPU&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The result is a fully client-side experimental platform for running recursive multi-agent systems on consumer hardware without requiring cloud GPUs.&lt;/p&gt;




&lt;h1&gt;
  
  
  The Problem with Traditional Multi-Agent Systems
&lt;/h1&gt;

&lt;p&gt;Most agent frameworks operate like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Agent A → generates text
         ↓
Agent B → reads text and generates more text
         ↓
Agent C → reads text and generates final answer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every handoff requires:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Token generation&lt;/li&gt;
&lt;li&gt;Token transmission&lt;/li&gt;
&lt;li&gt;Token re-processing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As the number of agents increases, the overhead grows rapidly.&lt;/p&gt;

&lt;p&gt;A significant portion of the computation is spent translating thoughts into text and then converting that text back into internal representations.&lt;/p&gt;

&lt;p&gt;This works, but it's not how neural networks naturally communicate.&lt;/p&gt;




&lt;h1&gt;
  
  
  What Is RecursiveMAS?
&lt;/h1&gt;

&lt;p&gt;RecursiveMAS takes a different approach.&lt;/p&gt;

&lt;p&gt;Instead of exchanging generated text, agents exchange their &lt;strong&gt;last-layer hidden states&lt;/strong&gt; (latent representations).&lt;/p&gt;

&lt;p&gt;Think of hidden states as the model's internal reasoning space before words are produced.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Agent A Hidden State
         ↓
RecursiveLink
         ↓
Agent B Hidden State
         ↓
RecursiveLink
         ↓
Agent C Hidden State
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The entire multi-agent system becomes a recursive computation graph operating in latent space.&lt;/p&gt;

&lt;p&gt;The original research introduces a lightweight component called &lt;strong&gt;RecursiveLink&lt;/strong&gt;, which acts as a bridge between agents.&lt;/p&gt;

&lt;p&gt;Rather than training or fine-tuning the underlying LLMs, only these small link modules are trained while the base models remain frozen.&lt;/p&gt;

&lt;p&gt;This allows multiple agents to collaboratively refine reasoning before any text is generated.&lt;/p&gt;




&lt;h1&gt;
  
  
  Core Concepts
&lt;/h1&gt;

&lt;h2&gt;
  
  
  RecursiveLink
&lt;/h2&gt;

&lt;p&gt;A lightweight residual network that transforms and transfers latent representations between agents.&lt;/p&gt;

&lt;p&gt;Instead of passing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"What is the answer?"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;agents pass:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[hidden_state_vector]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This dramatically reduces communication overhead.&lt;/p&gt;




&lt;h2&gt;
  
  
  Inner Link
&lt;/h2&gt;

&lt;p&gt;Allows an agent to recursively refine its own latent reasoning.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Agent
   ↓
Hidden State
   ↓
RecursiveLink
   ↓
Back Into Agent
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates iterative self-improvement loops before decoding text.&lt;/p&gt;




&lt;h2&gt;
  
  
  Outer Link
&lt;/h2&gt;

&lt;p&gt;Enables latent communication between different agents.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Agent A
   ↓
RecursiveLink
   ↓
Agent B
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The research demonstrates that even heterogeneous models can participate in these recursive workflows.&lt;/p&gt;




&lt;h2&gt;
  
  
  System-Level Recursion
&lt;/h2&gt;

&lt;p&gt;The entire multi-agent system can execute multiple refinement passes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Pass 1
   ↓
Pass 2
   ↓
Pass 3
   ↓
Final Decode
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of generating intermediate text after every step, the system performs latent collaboration first and produces text only at the end.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why This Matters
&lt;/h1&gt;

&lt;p&gt;According to the RecursiveMAS research, latent-space collaboration delivers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Higher benchmark accuracy&lt;/li&gt;
&lt;li&gt;Reduced token consumption&lt;/li&gt;
&lt;li&gt;Faster end-to-end inference&lt;/li&gt;
&lt;li&gt;Better scalability across multiple agents&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Reported results include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Up to 75% reduction in token usage&lt;/li&gt;
&lt;li&gt;1.2×–2.4× faster inference&lt;/li&gt;
&lt;li&gt;Average accuracy improvements across reasoning, coding, science, and medical benchmarks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The key insight is that agents can collaborate more efficiently when communication occurs inside the neural representation space rather than through natural language.&lt;/p&gt;




&lt;h1&gt;
  
  
  The Challenge: Running RecursiveMAS in the Browser
&lt;/h1&gt;

&lt;p&gt;The original RecursiveMAS implementation targets server environments and GPU inference stacks such as vLLM.&lt;/p&gt;

&lt;p&gt;Browser-based AI introduces a major limitation:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;WebLLM models do not normally expose internal hidden states.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Without access to hidden states, latent recursion is impossible.&lt;/p&gt;

&lt;p&gt;That became the motivation for this project.&lt;/p&gt;




&lt;h1&gt;
  
  
  Introducing recursiveMASWebLLM
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;recursiveMASWebLLM&lt;/strong&gt; is a specialized build pipeline for creating WebLLM models capable of latent-state transfer.&lt;/p&gt;

&lt;p&gt;It extends the browser AI stack to expose the information required for RecursiveMAS-style recursion.&lt;/p&gt;

&lt;p&gt;The goal is simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Research Paper
      ↓
Server GPU Implementation
      ↓
Browser-Compatible Runtime
      ↓
Accessible to Everyone
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  What This Project Adds
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Hidden State Extraction
&lt;/h2&gt;

&lt;p&gt;MLC-LLM is patched to expose:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;get_last_hidden&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows browser applications to access last-layer hidden states directly during inference.&lt;/p&gt;

&lt;p&gt;Without this capability, RecursiveMAS cannot function.&lt;/p&gt;




&lt;h2&gt;
  
  
  RecursiveLink Training Pipeline
&lt;/h2&gt;

&lt;p&gt;The repository includes tooling to train and package RecursiveLinks.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;train_recursivelink.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Generated links are exported as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;recursivelink.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These lightweight modules can then be loaded by browser-based agent systems.&lt;/p&gt;




&lt;h2&gt;
  
  
  Automated Browser Model Builds
&lt;/h2&gt;

&lt;p&gt;The build pipeline supports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Model conversion&lt;/li&gt;
&lt;li&gt;Quantization&lt;/li&gt;
&lt;li&gt;WebGPU compilation&lt;/li&gt;
&lt;li&gt;WASM generation&lt;/li&gt;
&lt;li&gt;Release packaging&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even small models can be built entirely through GitHub Actions without requiring local GPUs.&lt;/p&gt;




&lt;h2&gt;
  
  
  Browser Deployment
&lt;/h2&gt;

&lt;p&gt;Outputs include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.wasm
weights
recursivelink.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These artifacts can be hosted on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub Releases&lt;/li&gt;
&lt;li&gt;Hugging Face&lt;/li&gt;
&lt;li&gt;Static web hosting&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;and loaded directly into browser applications.&lt;/p&gt;




&lt;h1&gt;
  
  
  Project Architecture
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;recursiveMASWebLLM
        │
        ▼
Build Pipeline
        │
        ▼
.wasm + weights + recursivelink.json
        │
        ▼
Hosted Artifacts
        │
        ▼
RecursiveMAS Playground
        │
        ▼
Browser-Based Recursive Agents
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The builder generates everything needed for latent recursive collaboration in WebLLM-powered applications.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why Browser-Based Recursive Agents Are Interesting
&lt;/h1&gt;

&lt;h2&gt;
  
  
  1. Democratizing Advanced AI Research
&lt;/h2&gt;

&lt;p&gt;Researchers and developers can experiment with RecursiveMAS techniques without expensive cloud infrastructure.&lt;/p&gt;

&lt;p&gt;If a device supports WebGPU, it can participate.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Interactive Experimentation
&lt;/h2&gt;

&lt;p&gt;Developers can modify:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Recursion depth&lt;/li&gt;
&lt;li&gt;Agent roles&lt;/li&gt;
&lt;li&gt;Collaboration patterns&lt;/li&gt;
&lt;li&gt;Prompt strategies&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;and immediately observe how latent collaboration affects outcomes.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Education
&lt;/h2&gt;

&lt;p&gt;RecursiveMAS introduces a fundamentally different way of thinking about multi-agent systems.&lt;/p&gt;

&lt;p&gt;Running it locally in a browser makes it easier to understand and teach.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Lower Latency
&lt;/h2&gt;

&lt;p&gt;Reducing intermediate token generation is especially valuable in browser environments where responsiveness matters.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Future Extensions
&lt;/h2&gt;

&lt;p&gt;Exposing hidden states opens the door to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Latent planning systems&lt;/li&gt;
&lt;li&gt;Browser-side distillation&lt;/li&gt;
&lt;li&gt;Neural memory systems&lt;/li&gt;
&lt;li&gt;Hybrid cloud/browser agents&lt;/li&gt;
&lt;li&gt;Experimental reasoning architectures&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;RecursiveMAS is just one possible application.&lt;/p&gt;




&lt;h1&gt;
  
  
  Getting Started
&lt;/h1&gt;

&lt;p&gt;Repository:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/vishalmysore/recursiveMASWebLLM" rel="noopener noreferrer"&gt;https://github.com/vishalmysore/recursiveMASWebLLM&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The project includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Local build instructions&lt;/li&gt;
&lt;li&gt;GitHub Actions workflows&lt;/li&gt;
&lt;li&gt;RecursiveLink training utilities&lt;/li&gt;
&lt;li&gt;Model packaging tools&lt;/li&gt;
&lt;li&gt;Integration guidance for the RecursiveMAS playground&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Looking Ahead
&lt;/h1&gt;

&lt;p&gt;This project is still early, but it establishes the foundation for browser-native latent multi-agent systems.&lt;/p&gt;

&lt;p&gt;Future work includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Larger model support&lt;/li&gt;
&lt;li&gt;Improved model sharding&lt;/li&gt;
&lt;li&gt;Additional collaboration patterns&lt;/li&gt;
&lt;li&gt;Better WebGPU optimizations&lt;/li&gt;
&lt;li&gt;Community-created RecursiveLinks&lt;/li&gt;
&lt;li&gt;Integration with other browser AI frameworks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As browser AI continues to mature, I believe we'll see more experimentation move from cloud infrastructure to client-side environments.&lt;/p&gt;

&lt;p&gt;RecursiveMAS demonstrates that some of the most interesting ideas in AI may not require massive server clusters—they may eventually run directly in the browser.&lt;/p&gt;

&lt;p&gt;What do you think?&lt;/p&gt;

&lt;p&gt;Could latent-space multi-agent systems become the next evolution of browser AI experimentation?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/vishalmysore/recursiveMASWebLLM" rel="noopener noreferrer"&gt;https://github.com/vishalmysore/recursiveMASWebLLM&lt;/a&gt;&lt;br&gt;
&lt;a href="https://recursivemas.github.io/" rel="noopener noreferrer"&gt;https://recursivemas.github.io/&lt;/a&gt;&lt;br&gt;
&lt;a href="https://huggingface.co/VishalMysore/RecursiveMAS-0.5B-MLC" rel="noopener noreferrer"&gt;https://huggingface.co/VishalMysore/RecursiveMAS-0.5B-MLC&lt;/a&gt;&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>llm</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Stop Paying for Token APIs: How to Build a Serverless Multi-Agent Mesh in the Browser</title>
      <dc:creator>vishalmysore</dc:creator>
      <pubDate>Wed, 17 Jun 2026 12:04:28 +0000</pubDate>
      <link>https://dev.to/vishalmysore/stop-paying-for-token-apis-how-to-build-a-serverless-multi-agent-mesh-in-the-browser-54p8</link>
      <guid>https://dev.to/vishalmysore/stop-paying-for-token-apis-how-to-build-a-serverless-multi-agent-mesh-in-the-browser-54p8</guid>
      <description>&lt;p&gt;Every modern multi-agent architecture assumes a massive, expensive backend cloud infrastructure running hundreds of dollars in API token costs per hour. But what if an entire suite of specialized agents—Legal, Software, Security, Healthcare—could collaborate, negotiate, and execute complex tools completely localized inside consumer browser tabs, passing knowledge with zero intermediary servers?&lt;/p&gt;

&lt;p&gt;Welcome to &lt;strong&gt;agentHerd&lt;/strong&gt;—a radical paradigm shift in decentralized, sovereign artificial intelligence. &lt;/p&gt;

&lt;p&gt;By combining the local client-side execution power of &lt;strong&gt;WebGPU&lt;/strong&gt; with the direct, peer-to-peer networking capabilities of &lt;strong&gt;WebRTC&lt;/strong&gt;, agentHerd turns ordinary browser tabs into highly scalable, self-hosting AI environments. Zero cloud costs. Zero central servers. Total data privacy.&lt;/p&gt;




&lt;p&gt;Demo - &lt;a href="https://vishalmysore.github.io/agentHerd/" rel="noopener noreferrer"&gt;https://vishalmysore.github.io/agentHerd/&lt;/a&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  The agentHerd Stack at a Glance
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Inference:&lt;/strong&gt; WebLLM / WebGPU (Running Llama 3, Phi-3, or Gemma natively in the browser).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Networking:&lt;/strong&gt; Pure Serverless WebRTC Data Channels (Handshake via ephemeral URL hashes).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Data Isolation:&lt;/strong&gt; Distributed Federated RAG (Knowledge discovery without centralized vector stores).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Determinism:&lt;/strong&gt; Hybrid sandboxing (LLMs control personality/choice; sandboxed JavaScript handles immutable application rules).&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  1. The Core Paradox: Moving Inference and Networking to the Edge
&lt;/h2&gt;

&lt;p&gt;Traditional AI agents are cloud-bound because of two heavy dependencies: &lt;strong&gt;compute&lt;/strong&gt; (LLM inference) and &lt;strong&gt;orchestration&lt;/strong&gt; (state management and messaging).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Traditional Architecture]
Browser UI &amp;lt;---&amp;gt; Cloud Orchestrator &amp;lt;---&amp;gt; Vector DB &amp;lt;---&amp;gt; Expensive LLM APIs ($$$)

[agentHerd Architecture]
Browser Tab A (WebGPU + LLM) &amp;lt;======== WebRTC ========&amp;gt; Browser Tab B (WebGPU + LLM)
                                 (Direct P2P Link)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;agentHerd breaks this centralization by pushing both layers entirely to the client device:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;WebGPU for Compute:&lt;/strong&gt; Instead of querying external endpoints, models are cached locally and executed on the client's GPU via WebGPU. The moment a user opens a tab, their device becomes an active AI compute node.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;WebRTC for Orchestration:&lt;/strong&gt; Instead of a centralized message broker routing agent dialogue, tabs establish direct, encrypted peer-to-peer WebRTC data channels. &lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Serverless Signaling via URL Hashes
&lt;/h3&gt;

&lt;p&gt;WebRTC traditionally requires a signaling server to exchange Session Description Protocol (SDP) tokens. agentHerd implements an entirely serverless signaling option: the initial peer generates an SDP token encoded directly into a URL hash. Copying and sharing this URL establishes an absolute trust boundary—operating identically to an end-to-end encrypted chat room.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Federated Knowledge Retrieval (RAG) over WebRTC
&lt;/h2&gt;

&lt;p&gt;One of the greatest challenges of collaborative AI is data sharing. Uploading private company manuals or personal codebases to a central cloud database poses severe security risks. agentHerd solves this via &lt;strong&gt;Federated Knowledge Retrieval&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Peer A: Has "Legal_Doc.pdf"]             [Peer B: Needs Legal Context]
   |                                            |
   |-- 1. Generates Summary Card --------------&amp;gt;| (Broadcasts Summary to Mesh)
   |                                            |
   |                                            |-- 2. "I need data on Section 4"
   |&amp;lt;=- 3. Requests Chunk via WebRTC Data Channel-|
   |                                            |
   |-- 4. Runs Local RAG Engine                 |
   |-- 5. Extracts precise text snippet         |
   |                                            |
   |==- 6. Sends Answer Fragment via WebRTC ===&amp;gt;| (Received securely)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  How the Flow Works:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Local Extraction:&lt;/strong&gt; When a user uploads a document into their local agentHerd tab, the document never leaves their machine. The local browser model processes the file and generates a lightweight &lt;em&gt;Summary Card&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Summary Broadcast:&lt;/strong&gt; This abstract Summary Card is shared across the WebRTC mesh. Other peers know &lt;em&gt;what&lt;/em&gt; knowledge Peer A possesses, but they do not have the raw data.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;On-Demand Querying:&lt;/strong&gt; When Peer B's agent requires deep granular details to answer a prompt, it queries Peer A over the direct WebRTC data channel.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Localized Verification:&lt;/strong&gt; Peer A’s local RAG system searches its own memory space, extracts the specific matching snippet, passes it through its own WebGPU instance, and returns &lt;em&gt;only&lt;/em&gt; the specific answer fragment to Peer B.&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The Sovereign AI Rule:&lt;/strong&gt; You hand the network your answers—never your data.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  3. The Action Layer: Where Determinism Meets Personality
&lt;/h2&gt;

&lt;p&gt;Generative models are inherently non-deterministic. If you ask an LLM to play chess against another LLM purely through text generation, the game will rapidly degrade into illegal moves and hallucinated board positions.&lt;/p&gt;

&lt;p&gt;agentHerd solves this by splitting responsibilities through a strict separation of concerns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;The Persona (LLM):&lt;/strong&gt; Manages choices, dialogue, strategic goals, and social banter.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;The Guardrail (Deterministic Engine):&lt;/strong&gt; An immutable, sandboxed environment (like a localized &lt;code&gt;chess.js&lt;/code&gt; script) that enforces absolute operational rules.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When an agent wants to perform an action, it cannot arbitrary alter the state. It must output a structured JSON command envelope that is verified by every node in the mesh:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sender"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Agent_Alpha_Chess"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"timestamp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1718619837&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"EXECUTE_TOOL"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"payload"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"tool_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CHESS_MOVE"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"arguments"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"from"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"e2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"to"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"e4"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"signature"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0x7f83b..."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;Agent_Alpha&lt;/code&gt; attempts to generate an illegal move, the deterministic script running on the peer nodes instantly rejects the packet, ensuring the integrity of the environment without requiring a central server to referee the state.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Operational Boundaries and Engineering Realities
&lt;/h2&gt;

&lt;p&gt;Building completely within the constraints of a browser environment requires engineering trade-offs. Developers looking to leverage this stack should be aware of current boundaries:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;VRAM and Model Swapping:&lt;/strong&gt; Running models like Llama 3 (8B) or Phi-3 requires a modern GPU with sufficient VRAM. Attempting to open multiple heavy-inference browser tabs concurrently can saturate hardware resources. Multi-agent rooms perform best when using highly optimized 3B or smaller models optimized for web runtimes.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;NAT Traversal &amp;amp; Corporate Firewalls:&lt;/strong&gt; While public STUN servers successfully resolve connections for the majority of consumer network topologies, strict enterprise environments utilizing symmetric NATs often block direct WebRTC channels. In these scenarios, falling back to a dedicated, self-hosted TURN relay server becomes mandatory to handle the traffic.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Topology Scaling Limits:&lt;/strong&gt; Because each browser tab must maintain WebRTC connections with other agents, a full-mesh topology (where every node connects to every other node) hits a browser-imposed performance wall as the group size scales. For massive clusters, the architecture transitions toward hybrid star/relay networks.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  5. Join the Decentralized Frontier
&lt;/h2&gt;

&lt;p&gt;The future of multi-agent collaboration isn't a massive corporate data center burning megawatts of energy to route your private API calls—it's the browser tab you already have open. &lt;/p&gt;

&lt;p&gt;agentHerd proves that we can build highly complex, deeply collaborative, and perfectly private AI ecosystems using the open web standard tools already at our disposal. &lt;/p&gt;

&lt;p&gt;The project is fully open-source and welcoming contributors. We are actively looking for developers to help build out new specialized agent domains, create native CLI-peer wrappers, and engineer custom tool integrations.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Explore the Codebase:&lt;/strong&gt; &lt;a href="https://github.com/vishalmysore/agentHerd" rel="noopener noreferrer"&gt;agentHerd on GitHub&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Launch a Room:&lt;/strong&gt; Open the repository, generate your signaling hash, and invite your first agent herd today.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Contribute:&lt;/strong&gt; Star the repo, open an issue, and let's build an unstoppable, serverless AI collective together.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Important distinction: agentHerd distributes cognition, not computation.&lt;br&gt;
Each node runs its own model independently. The system does not combine GPUs to run larger models—it coordinates many smaller, autonomous agents working in parallel.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>serverless</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Foundation vs. Instruct vs. Chat Models: One Question, Three Answers</title>
      <dc:creator>vishalmysore</dc:creator>
      <pubDate>Tue, 16 Jun 2026 23:08:32 +0000</pubDate>
      <link>https://dev.to/vishalmysore/foundation-vs-instruct-vs-chat-models-one-question-three-answers-3gi</link>
      <guid>https://dev.to/vishalmysore/foundation-vs-instruct-vs-chat-models-one-question-three-answers-3gi</guid>
      <description>&lt;p&gt;&lt;em&gt;A hands-on tutorial you can run for free in Google Colab.&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Run it yourself:&lt;/strong&gt; open &lt;a href="https://github.com/vishalmysore/AI/blob/main/foundation_instruct_chat_tutorial.ipynb" rel="noopener noreferrer"&gt;&lt;code&gt;foundation_instruct_chat_tutorial.ipynb&lt;/code&gt;&lt;/a&gt; in Google Colab and run every cell top to bottom. It uses the &lt;strong&gt;SmolLM2-135M&lt;/strong&gt; family — small enough for a free CPU runtime, no GPU needed.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Why this confuses everyone
&lt;/h2&gt;

&lt;p&gt;People say "LLM," "GPT," "an AI model," and "ChatGPT" as if they were the same thing. They aren't. There's a ladder of training stages between "a model that read the internet" and "an assistant you can chat with," and the words &lt;strong&gt;foundation&lt;/strong&gt;, &lt;strong&gt;instruct&lt;/strong&gt;, and &lt;strong&gt;chat&lt;/strong&gt; mark the rungs.&lt;/p&gt;

&lt;p&gt;The cleanest way to feel the difference is to do something deliberately unfair: ask the &lt;strong&gt;exact same question&lt;/strong&gt; to three versions of the &lt;strong&gt;same model family&lt;/strong&gt; and watch how differently they behave. Our question is deliberately boring so the &lt;em&gt;behavior&lt;/em&gt; stands out:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"What is the capital of France?"&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We use three checkpoints from Hugging Face's SmolLM2 family:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model type&lt;/th&gt;
&lt;th&gt;Hugging Face ID&lt;/th&gt;
&lt;th&gt;One-line summary&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Foundation (base)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;HuggingFaceTB/SmolLM2-135M&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Predicts the next token. Knows things, isn't helpful.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Instruct&lt;/td&gt;
&lt;td&gt;&lt;code&gt;HuggingFaceTB/SmolLM2-135M-Instruct&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Fine-tuned to follow a single instruction.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Chat&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;HuggingFaceTB/SmolLM2-135M-Instruct&lt;/code&gt; (used conversationally)&lt;/td&gt;
&lt;td&gt;Same weights, driven through a multi-turn message list.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Notice that the chat row reuses the instruct checkpoint. That's not a shortcut — it's the honest reality, and we'll come back to why.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. The foundation model: a brilliant autocomplete
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;foundation model&lt;/strong&gt; (also called a &lt;em&gt;base&lt;/em&gt; or &lt;em&gt;pretrained&lt;/em&gt; model) is trained on exactly one objective: given a stretch of text, &lt;strong&gt;predict the next token&lt;/strong&gt;. Nothing else. It reads a huge slice of the internet and gets very good at continuing text in a statistically plausible way.&lt;/p&gt;

&lt;p&gt;What it is &lt;em&gt;never&lt;/em&gt; taught is that a question deserves an answer. So when you feed it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;What is the capital of France?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;it doesn't think &lt;em&gt;"I should answer that."&lt;/em&gt; It thinks &lt;em&gt;"On the internet, what usually **comes after&lt;/em&gt;* a line like this?"* And the answer is often… &lt;strong&gt;more quiz questions&lt;/strong&gt;, a worksheet, or a tangent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;What is the capital of France? What is the capital of Germany? What is the
capital of Italy? ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the notebook we pass the raw string straight into the pipeline with no formatting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;base_pipe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text-generation&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;HuggingFaceTB/SmolLM2-135M&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;base_raw_out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;base_pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;test_query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_new_tokens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;do_sample&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base_raw_out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;generated_text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Takeaway:&lt;/strong&gt; a foundation model is a &lt;strong&gt;text completer&lt;/strong&gt;, not an assistant. It contains enormous knowledge but has no concept of being &lt;em&gt;helpful&lt;/em&gt;. It's the raw clay everything else is shaped from.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. The instruct model: teaching the model to answer
&lt;/h2&gt;

&lt;p&gt;An &lt;strong&gt;instruct model&lt;/strong&gt; starts from that same base model and goes through a second stage of training — &lt;strong&gt;fine-tuning on (instruction → response) pairs&lt;/strong&gt;. Thousands to millions of examples of the shape &lt;em&gt;"Here's a request. Here's a good response."&lt;/em&gt; This teaches the model a new contract: &lt;strong&gt;when the user asks for something, actually do it and then stop.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;But there's a crucial detail people miss: an instruct model only behaves correctly when you wrap your text in the &lt;strong&gt;exact special format it was trained on.&lt;/strong&gt; That format uses control tokens — for SmolLM2 they look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;|im_start|&amp;gt;user
What is the capital of France?&amp;lt;|im_end|&amp;gt;
&amp;lt;|im_start|&amp;gt;assistant
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You don't type those tokens by hand. Every instruct model ships with a &lt;strong&gt;chat template&lt;/strong&gt; baked into its tokenizer that builds them for you:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;tokenizer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AutoTokenizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_pretrained&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;instruct_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;formatted_prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tokenizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;apply_chat_template&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;test_query&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
    &lt;span class="n"&gt;tokenize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;add_generation_prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# appends the 'assistant' cue
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Feed &lt;em&gt;that&lt;/em&gt; to the same-sized model and you get a clean, direct answer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The capital of France is Paris.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The notebook prints the formatted prompt &lt;strong&gt;before&lt;/strong&gt; generating, so you can literally see the hidden scaffolding the model receives. That "aha" — &lt;em&gt;oh, there's a whole structure under the hood&lt;/em&gt; — is the most important thing in the tutorial.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Takeaway:&lt;/strong&gt; an instruct model = a base model &lt;strong&gt;+ instruction tuning + a required prompt format&lt;/strong&gt;. Skip the format and even a well-trained instruct model can fall back to rambling.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. The chat model: memory across turns
&lt;/h2&gt;

&lt;p&gt;Here's the part that surprises people: a &lt;strong&gt;chat model is usually the same weights as the instruct model.&lt;/strong&gt; The difference isn't &lt;em&gt;what&lt;/em&gt; the model is — it's &lt;em&gt;how you drive it.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Instead of one instruction in, one response out, you maintain a &lt;strong&gt;running list of role-tagged messages&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;chat_history&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;What is the capital of France?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;chat_out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;chat_pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chat_history&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_new_tokens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The pipeline applies the chat template for you and returns the &lt;strong&gt;whole conversation&lt;/strong&gt; with the assistant's reply appended. For a single turn, that looks identical to the instruct example. The magic only appears when the conversation &lt;strong&gt;continues&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So in the notebook we append the reply and ask a deliberately vague follow-up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;conversation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chat_out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;generated_text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;        &lt;span class="c1"&gt;# user + assistant so far
&lt;/span&gt;&lt;span class="n"&gt;conversation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                     &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;And what is a famous landmark there?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="n"&gt;follow_up&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;chat_pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conversation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_new_tokens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The word &lt;strong&gt;"there"&lt;/strong&gt; is meaningless on its own. But because we passed the &lt;em&gt;entire history&lt;/em&gt;, the model resolves "there" → &lt;strong&gt;Paris&lt;/strong&gt; and names a landmark. That carried-over context is what turns a one-shot Q&amp;amp;A into something that feels like a conversation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Takeaway:&lt;/strong&gt; a chat model is an instruct model &lt;strong&gt;driven through a multi-turn message list&lt;/strong&gt;, so each new turn can use the previous turns as context. The system prompt, the &lt;code&gt;user&lt;/code&gt;/&lt;code&gt;assistant&lt;/code&gt; roles, and the growing history are the "chat" part.&lt;/p&gt;




&lt;h2&gt;
  
  
  The whole picture in one table
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Trained to…&lt;/th&gt;
&lt;th&gt;You give it…&lt;/th&gt;
&lt;th&gt;Reply to &lt;em&gt;"What is the capital of France?"&lt;/em&gt;
&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Foundation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;continue text&lt;/td&gt;
&lt;td&gt;a raw string&lt;/td&gt;
&lt;td&gt;echoes / continues the document — may never answer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Instruct&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;follow one instruction&lt;/td&gt;
&lt;td&gt;a chat-templated string&lt;/td&gt;
&lt;td&gt;a direct answer: &lt;em&gt;"The capital of France is Paris."&lt;/em&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Chat&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;converse over many turns&lt;/td&gt;
&lt;td&gt;a list of messages&lt;/td&gt;
&lt;td&gt;a direct answer &lt;strong&gt;+ remembers context&lt;/strong&gt; for follow-ups&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Read top to bottom, it's a progression, not three unrelated things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Foundation&lt;/strong&gt; learns the world by predicting text.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Instruct&lt;/strong&gt; fine-tunes that knowledge into &lt;em&gt;do-what-I-ask&lt;/em&gt; behavior — and demands a specific prompt format.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chat&lt;/strong&gt; wraps the instruct model in a &lt;em&gt;multi-turn interface&lt;/em&gt; so context flows across turns.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When you talk to a commercial assistant, you're using stage 3, sitting on stage 2, built on stage 1.&lt;/p&gt;




&lt;h2&gt;
  
  
  A note on honesty and scale
&lt;/h2&gt;

&lt;p&gt;SmolLM2-135M is &lt;strong&gt;tiny&lt;/strong&gt; — about 135 million parameters, versus the tens or hundreds of &lt;em&gt;billions&lt;/em&gt; in frontier models. At this size the model will sometimes get a fact wrong, repeat itself, or trail off. &lt;strong&gt;That's expected, and it's not the point.&lt;/strong&gt; The tutorial is designed to make the &lt;em&gt;behavioral&lt;/em&gt; gap between the three modes visible on a free laptop or Colab CPU — not to win a trivia contest. The exact same three-stage structure scales all the way up to the largest models in production.&lt;/p&gt;




&lt;h2&gt;
  
  
  Run it and tinker
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Open &lt;a href="//foundation_instruct_chat_tutorial.ipynb"&gt;&lt;code&gt;foundation_instruct_chat_tutorial.ipynb&lt;/code&gt;&lt;/a&gt; in Google Colab (&lt;code&gt;File → Open notebook → Upload&lt;/code&gt;, or push it to GitHub and use the Colab badge).&lt;/li&gt;
&lt;li&gt;Run all cells (&lt;code&gt;Runtime → Run all&lt;/code&gt;). The first run downloads the models — give it a minute.&lt;/li&gt;
&lt;li&gt;Experiment:

&lt;ul&gt;
&lt;li&gt;Change &lt;code&gt;test_query&lt;/code&gt; to something open-ended like &lt;code&gt;"Write a haiku about the sea."&lt;/code&gt; and watch how the three modes diverge even more.&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;do_sample=True&lt;/code&gt; with &lt;code&gt;temperature=0.7&lt;/code&gt; for more varied, creative output.&lt;/li&gt;
&lt;li&gt;Swap in a larger sibling such as &lt;code&gt;HuggingFaceTB/SmolLM2-360M-Instruct&lt;/code&gt; and feel the quality jump.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Once you've &lt;em&gt;seen&lt;/em&gt; the three behaviors with your own eyes, the vocabulary — base, instruct, chat, chat template, system prompt — stops being jargon and starts being obvious.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Happy experimenting!&lt;/em&gt; 🚀&lt;/p&gt;

</description>
      <category>ai</category>
      <category>beginners</category>
      <category>llm</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
