<?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: Meshe Damilola Peter</title>
    <description>The latest articles on DEV Community by Meshe Damilola Peter (@meshemugles).</description>
    <link>https://dev.to/meshemugles</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%2F1067106%2F5a912772-3250-4ece-a58d-ca0ca0476194.png</url>
      <title>DEV Community: Meshe Damilola Peter</title>
      <link>https://dev.to/meshemugles</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/meshemugles"/>
    <language>en</language>
    <item>
      <title>I added Rasa Mantle to my VS Code extension: tool calling, memory and LLMs</title>
      <dc:creator>Meshe Damilola Peter</dc:creator>
      <pubDate>Sun, 18 Oct 2026 02:15:34 +0000</pubDate>
      <link>https://dev.to/meshemugles/i-added-rasa-mantle-to-my-vs-code-extension-tool-calling-memory-and-llms-3obd</link>
      <guid>https://dev.to/meshemugles/i-added-rasa-mantle-to-my-vs-code-extension-tool-calling-memory-and-llms-3obd</guid>
      <description>&lt;p&gt;I keep my project notes inside VS Code with an extension I built called &lt;a href="https://marketplace.visualstudio.com/items?itemName=MESHEmugles1.notevs" rel="noopener noreferrer"&gt;NoteVs&lt;/a&gt;. For the &lt;a href="https://github.com/RasaHQ/rasa-community-resources/tree/main/heroes" rel="noopener noreferrer"&gt;Rasa Heroes&lt;/a&gt; program I gave it a conversational agent built on &lt;strong&gt;&lt;a href="https://rasa.com/" rel="noopener noreferrer"&gt;Rasa&lt;/a&gt; Mantle&lt;/strong&gt;, Rasa's new agent engine. Now I can ask &lt;em&gt;"what did I annotate in the auth module?"&lt;/em&gt; or &lt;em&gt;"remind me about the refactor note tomorrow"&lt;/em&gt; from a chat panel next to my code.&lt;/p&gt;

&lt;p&gt;This isn't really a post about notes, though. It covers the three parts of Mantle I spent the most time with: &lt;strong&gt;tool calling, memory, and the LLM layer&lt;/strong&gt;. That includes running it on Groq, which Rasa doesn't list as a named provider. I'll cover what each part does, how I used it, and what broke.&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%2Fth9b6ph7dcxrrirqunel.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%2Fth9b6ph7dcxrrirqunel.png" alt="Screenshot 1: the NoteVs Agent panel listing my two notes" width="800" height="429"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&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%2Fnobfzb1c9spubilw7wbz.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%2Fnobfzb1c9spubilw7wbz.png" alt="Screenshot 2: asking it to delete " width="800" height="429"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The setup in 30 seconds
&lt;/h2&gt;

&lt;p&gt;Everything runs locally. The VS Code extension owns the notes and exposes a tiny HTTP endpoint on &lt;code&gt;localhost:37492&lt;/code&gt;. The agent is a local Rasa server whose tools call that endpoint. The extension's chat panel talks to Rasa over its REST channel.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rasa-notevs-agent/
├─ agent.yml              ← persona + global rules
├─ skills/*/skill.md      ← 4 skills
├─ tools/notevs_tools.py  ← 10 tools
├─ memory.yml             ← project memory
└─ integrations.yml       ← LLM + channels
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I first built this on classic CALM: flows, slots and slot mappings, about &lt;strong&gt;590 lines of YAML across five files&lt;/strong&gt;. The Mantle version is about &lt;strong&gt;210 lines, mostly plain English&lt;/strong&gt;. Here's where that difference comes from.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Tool calling
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Tools are typed Python functions
&lt;/h3&gt;

&lt;p&gt;A Mantle tool is an async function with a decorator. The type hints and description are what the LLM sees.&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="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;rasa.calm_v2.tools.decorator&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ToolContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;rasa.calm_v2.tools.result&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ToolResult&lt;/span&gt;

&lt;span class="nd"&gt;@tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Search notes by keyword across title, content, tags, and annotation comments.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;search_notes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ToolContext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ToolResult&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;_call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;notevs_search_notes&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;query&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All 10 of my tools are thin wrappers like this around one helper that POSTs to the extension. The most useful decision was making failures readable instead of raising:&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;except&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTPError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;ToolResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;llm_response&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;ok&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;notevs_extension_unreachable: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="si"&gt;}&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;The LLM sees &lt;code&gt;notevs_extension_unreachable&lt;/code&gt; and the skill tells it what to say: &lt;em&gt;"NoteVs doesn't seem to be running."&lt;/em&gt; The user never sees a stack trace.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pass what a tool needs as an argument
&lt;/h3&gt;

&lt;p&gt;My worst bug lived here. With several VS Code windows open, every window's agent was reading the same project. The extension's HTTP server uses a fixed port, so only one window wins it, and that window answered every tool call. I found out when an agent listed notes from a completely different project.&lt;/p&gt;

&lt;p&gt;The fix was for the helper to send the project folder &lt;strong&gt;explicitly as an argument on every call&lt;/strong&gt;, taken from an env var each window sets for its own agent. If a tool needs a value to be correct, pass it in. Don't rely on the tool's surroundings to supply it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Skills decide which tools are in play
&lt;/h3&gt;

&lt;p&gt;A skill is a &lt;code&gt;skill.md&lt;/code&gt; file. Front matter lists the tools it can use, and the body is instructions. Each skill only imports what it needs, so the model never picks from all 10 tools at once:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Note Management&lt;/span&gt;
&lt;span class="na"&gt;import_tools&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;create_note&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;save_note&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;add_annotation&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;delete_note&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;list_notes&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;search_notes&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;get_note&lt;/span&gt;
&lt;span class="na"&gt;tool_constraints&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;delete_note&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;requires_confirmation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

If the user references a note by title only, look it up first with
&lt;span class="sb"&gt;`search_notes`&lt;/span&gt; or &lt;span class="sb"&gt;`list_notes`&lt;/span&gt; to get its real id ... never invent an id.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Confirmation is enforced by the framework
&lt;/h3&gt;

&lt;p&gt;That &lt;code&gt;tool_constraints&lt;/code&gt; block replaced an entire classic-CALM flow: collect the id, collect the title, collect a yes/no, branch, call, reset slots. In Mantle it's four lines. &lt;strong&gt;Deletes, reminders and Notion/Obsidian exports can't run until the user confirms, whatever the model decides.&lt;/strong&gt; Read-only tools have no constraint, so &lt;em&gt;"find my bug notes and show me the newest"&lt;/em&gt; can chain search → get in one turn.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What broke:&lt;/strong&gt; the very first message of every conversation got swallowed. Mantle's built-in session-start skill greets the user and ends the turn, so &lt;em&gt;"hey, can you list my notes"&lt;/em&gt; got a hello and no tool call. I overrode it with a skill that does nothing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Session Start&lt;/span&gt;
&lt;span class="na"&gt;routing&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;engine_managed&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
:::ordered_block id=main
steps:
&lt;span class="p"&gt;  -&lt;/span&gt; id: skip
    noop: true
    next: END
:::
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that, the first message goes straight to the right skill and its tool.&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://mantle.rasa.com/concepts/memory" rel="noopener noreferrer"&gt;Mantle memory&lt;/a&gt; is the set of values a skill keeps track of during a conversation. Every entry has to be declared in a &lt;code&gt;memory.yml&lt;/code&gt; first, and tools write to it with &lt;code&gt;context.memory.set(...)&lt;/code&gt;. The LLM can only write entries you mark &lt;code&gt;llm_settable&lt;/code&gt;, so it can't make up something like a verification result. Values shared between skills go in a project-level &lt;code&gt;memory.yml&lt;/code&gt;, and instructions can branch on a value (&lt;code&gt;if: session.project.&amp;lt;entry&amp;gt; == ...&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;I only used it lightly. My project memory has one field, the workspace folder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;folder_path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;text&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Absolute&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;path&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;of&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;VS&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Code&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;workspace&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;this&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;conversation&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;is&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;scoped&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;to."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tools copy the folder path into it, so I can see which project a conversation belongs to in &lt;code&gt;rasa inspect&lt;/code&gt;. Project fields lock once they're set, which suits a value that never changes during a session.&lt;/p&gt;

&lt;p&gt;Where I'd take it next: remembering the note we're talking about, so &lt;em&gt;"delete that one"&lt;/em&gt; two turns later doesn't need another search, and using memory as a skill precondition, so exports only start once an integration is connected. If you want to try it, the &lt;a href="https://mantle.rasa.com/concepts/memory" rel="noopener noreferrer"&gt;memory docs&lt;/a&gt; cover the full schema.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The LLM layer (and running on Groq)
&lt;/h2&gt;

&lt;p&gt;The model is set in &lt;code&gt;integrations.yml&lt;/code&gt;. OpenAI and Anthropic are Rasa's named providers, but I wanted Groq for its speed in a chat panel. Groq isn't on the list.&lt;/p&gt;

&lt;p&gt;Before giving up, I read Rasa's provider mapping in the installed package (&lt;code&gt;rasa/shared/providers/mappings.py&lt;/code&gt;). Unnamed providers go through &lt;strong&gt;LiteLLM&lt;/strong&gt;, which supports Groq, so this works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;llm&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;groq&lt;/span&gt;
  &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;qwen/qwen3.8-27b&lt;/span&gt;
  &lt;span class="na"&gt;api_key_env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;GROQ_API_KEY&lt;/span&gt;
  &lt;span class="na"&gt;reasoning_effort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;none&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Getting to that config took three model changes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;openai/gpt-oss-120b&lt;/code&gt;&lt;/strong&gt; failed Mantle's strict tool-schema validation twice in one session. Once it called a tool that doesn't exist, and once it tried to activate a skill id that wasn't in the allowed list. Both were real 400s that killed the turn.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;llama-3.3-70b-versatile&lt;/code&gt;&lt;/strong&gt; was listed in Groq's docs as supporting tool calls, but a live &lt;code&gt;GET /openai/v1/models&lt;/code&gt; showed my account never had access to it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Qwen&lt;/strong&gt; was the first to complete a full &lt;code&gt;activate → search_notes → result&lt;/code&gt; turn correctly. It thinks by default, though, and its reasoning leaked into chat replies, not always inside &lt;code&gt;&amp;lt;think&amp;gt;&lt;/code&gt; tags. Setting &lt;strong&gt;&lt;code&gt;reasoning_effort: none&lt;/code&gt;&lt;/strong&gt; turns thinking mode off instead of hiding it. A month later Groq replaced &lt;code&gt;qwen3.6-27b&lt;/code&gt; with &lt;code&gt;qwen3.8-27b&lt;/code&gt; and I had to re-pin.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Lessons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;An agent whose whole job is tool calling needs a model that is genuinely good at tool calling.&lt;/strong&gt; Speed doesn't matter if the turn dies on a schema error.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make the provider swappable.&lt;/strong&gt; Since it all goes through LiteLLM, I added a small script that rewrites the &lt;code&gt;llm:&lt;/code&gt; block at startup from an &lt;code&gt;LLM_PROVIDER&lt;/code&gt; env var. Groq, OpenAI and Anthropic are now each one setting away. Users of the extension pick one in Settings.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The catch: I'm on Groq's free tier
&lt;/h3&gt;

&lt;p&gt;I run this on a free Groq API key, and you can see what that costs in the screenshots above. In the second one, I ask the agent to delete "Note of this". It asks me to confirm, I say "Yeah go ahead", and the note disappears from the sidebar. Then I ask "is it removed?" and get no answer, just the panel's fallback message. Groq had rate-limited the request; the extension's Output channel shows the response with &lt;code&gt;retry-after: 9&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The delete worked. Only the LLM's next reply failed.&lt;/strong&gt; On a free key, a few quick messages in a row can hit the limit, and the agent then misses a reply or answers late. I think the same limit explains why the first screenshot needed an "are you done?" nudge before the list came back.&lt;/p&gt;

&lt;p&gt;That's a limit of the free tier, not of Mantle. A paid Groq key, or switching &lt;code&gt;LLM_PROVIDER&lt;/code&gt; to OpenAI or Anthropic, removes it. NoteVs itself doesn't need an LLM at all: notes, annotations and search all work from the sidebar. The agent is an optional layer on top.&lt;/p&gt;

&lt;h2&gt;
  
  
  One config trap
&lt;/h2&gt;

&lt;p&gt;With &lt;code&gt;inspector.enabled: true&lt;/code&gt; in &lt;code&gt;integrations.yml&lt;/code&gt;, &lt;code&gt;rasa run&lt;/code&gt; printed "Rasa server is up and running" and then crashed with &lt;code&gt;EADDRINUSE&lt;/code&gt;. The inspector is for the separate &lt;code&gt;rasa inspect&lt;/code&gt; command. Under &lt;code&gt;rasa run&lt;/code&gt; it tries to bind the same port the REST channel just took. I keep it off in the running config and use &lt;code&gt;rasa inspect --debug&lt;/code&gt; when I want a turn-by-turn trace of every LLM and tool call, which is how I found most of the bugs above.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;NoteVs&lt;/strong&gt; on the &lt;a href="https://marketplace.visualstudio.com/items?itemName=MESHEmugles1.notevs" rel="noopener noreferrer"&gt;VS Code Marketplace&lt;/a&gt;. Add your LLM and Rasa keys in Settings - &lt;strong&gt;Rasa Mantle&lt;/strong&gt; You can find the docs &lt;a href="https://mantle.rasa.com/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build your own&lt;/strong&gt; starting at &lt;a href="https://rasa.com/" rel="noopener noreferrer"&gt;rasa.com&lt;/a&gt; with a free Developer Edition licence and the examples in &lt;a href="https://github.com/RasaHQ/rasa-community-resources" rel="noopener noreferrer"&gt;rasa-community-resources&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A note on versions: this is all on a pre-release build (&lt;code&gt;rasa-pro==3.19.0.dev5&lt;/code&gt;). Pin your version and expect things to move.&lt;/p&gt;

&lt;p&gt;This was built for &lt;strong&gt;Rasa Heroes&lt;/strong&gt;, Rasa's community program for developers building with the new Rasa. If you're building with Mantle, especially on a provider other than OpenAI or Anthropic, I'd like to hear how it's going in the comments.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>vscode</category>
      <category>rasa</category>
    </item>
  </channel>
</rss>
