<?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: Pandas Studio</title>
    <description>The latest articles on DEV Community by Pandas Studio (@pandas_studio_d473619e281).</description>
    <link>https://dev.to/pandas_studio_d473619e281</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%2F4171538%2F01573879-3256-40dd-8bc9-6914caea0802.png</url>
      <title>DEV Community: Pandas Studio</title>
      <link>https://dev.to/pandas_studio_d473619e281</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pandas_studio_d473619e281"/>
    <language>en</language>
    <item>
      <title>[OpenShell] Fixing OpenShell MainProcessExited Error via DB Bypass</title>
      <dc:creator>Pandas Studio</dc:creator>
      <pubDate>Sun, 25 Oct 2026 15:24:19 +0000</pubDate>
      <link>https://dev.to/pandas_studio_d473619e281/openshell-fixing-openshell-mainprocessexited-error-via-db-bypass-49li</link>
      <guid>https://dev.to/pandas_studio_d473619e281/openshell-fixing-openshell-mainprocessexited-error-via-db-bypass-49li</guid>
      <description>&lt;p&gt;After a system reboot, my sandbox became stuck in a permanent Error state, blocking all management operations. I learned how to bypass this state by manually patching the gateway database and how to prevent it using systemd service ordering.&lt;/p&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;I run a Hermes Agent (v0.20.6) inside a sandbox named &lt;code&gt;my-claw&lt;/code&gt; on an NVIDIA DGX Spark. The environment uses NemoClaw (v0.0.124) which is pinned to OpenShell v0.0.116. Management of the sandbox is handled via the &lt;code&gt;openshell&lt;/code&gt; and &lt;code&gt;nemohermes&lt;/code&gt; CLI tools, which communicate with a gateway service.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happened
&lt;/h2&gt;

&lt;p&gt;Following a reboot on 2026-10-24, the sandbox phase was stuck at &lt;code&gt;Error&lt;/code&gt; (MainProcessExited). This error state caused all management commands—&lt;code&gt;exec&lt;/code&gt;, &lt;code&gt;snapshot&lt;/code&gt;, &lt;code&gt;stop&lt;/code&gt;, &lt;code&gt;start&lt;/code&gt;, and &lt;code&gt;rebuild&lt;/code&gt;—to be rejected. While the agent's internal processes like Hermes, cron, and Telegram continued to run, the daily backup (sandbox-backup.service) failed because it could not execute commands within the sandbox. Specifically, &lt;code&gt;nemohermes exec&lt;/code&gt; for deployment and &lt;code&gt;openshell sandbox download&lt;/code&gt; were both blocked.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I tried
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Manual data backup via &lt;code&gt;docker exec&lt;/code&gt;&lt;/strong&gt; → Success. I bypassed the management tools to extract 183 files (1.4M), .hermes files (4,094 files, 36M), and other data (271M) directly from the container. This provided a fallback when the standard backup failed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Standard management commands (&lt;code&gt;stop&lt;/code&gt;, &lt;code&gt;start&lt;/code&gt;, &lt;code&gt;rebuild&lt;/code&gt;)&lt;/strong&gt; → Failed. The system refused commands with errors stating the sandbox must be in a specific state (e.g., 'must be Ready to stop' or 'must be Stopped to start'). I also tried &lt;code&gt;rebuild&lt;/code&gt;, but it failed because the step to read MCP settings requires &lt;code&gt;exec&lt;/code&gt;, which was blocked.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Restarting the gateway service&lt;/strong&gt; → Failed. The supervisor reconnected immediately, but the phase remained in &lt;code&gt;Error&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stopping the container with &lt;code&gt;docker stop&lt;/code&gt;&lt;/strong&gt; → Failed. The phase did not transition to &lt;code&gt;Stopped&lt;/code&gt;, and the error persisted. This attempt caused an 11-minute interruption to Hermes and Telegram while the container was down.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Root cause
&lt;/h2&gt;

&lt;p&gt;OpenShell v0.0.116 has a bug where if the main process terminates while the sandbox is in a &lt;code&gt;Ready&lt;/code&gt; state (as happened during the reboot), it records a &lt;code&gt;MainProcessExited&lt;/code&gt; error. In this specific version, &lt;code&gt;Error&lt;/code&gt; is treated as a terminal state that cannot be exited through standard commands; it can only be cleared by deleting the sandbox entirely. While this was fixed in v0.1.5 (PR #4231), NemoClaw pins the version to 0.0.116.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix
&lt;/h2&gt;

&lt;p&gt;Instead of the destructive 'destroy and onboard' approach, I developed a Python script to perform a surgical 1-byte modification in the gateway's SQLite database. By changing the phase value from &lt;code&gt;Error&lt;/code&gt; (3) to &lt;code&gt;Stopped&lt;/code&gt; (7), I tricked the gateway into allowing a standard &lt;code&gt;start&lt;/code&gt; sequence. To prevent recurrence, I implemented a &lt;code&gt;sandbox-clean-stop.service&lt;/code&gt; that triggers an &lt;code&gt;openshell sandbox stop&lt;/code&gt; during system shutdown, ensuring the sandbox reaches a clean &lt;code&gt;Stopped&lt;/code&gt; state before the host goes down. I also had to fix a systemd ordering cycle where the unit was being dropped at boot because of a &lt;code&gt;WantedBy=default.target&lt;/code&gt; conflict.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Surgical DB phase fix logic&lt;/em&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;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;off&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;STOPPED&lt;/span&gt;
&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;update objects set payload=? where rowid=?&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="nf"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;rowid&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Clean stop service configuration&lt;/em&gt;&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="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;Unit&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="na"&gt;Description=nemo-ops&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cleanly stop my-claw at shutdown (OpenShell 0.0.116 sticks in Error if it goes down while Ready)&lt;/span&gt;
&lt;span class="c1"&gt;# Units stop in reverse After= order, so this stops before the gateway.&lt;/span&gt;
&lt;span class="s"&gt;After=nemoclaw-openshell-gateway.service&lt;/span&gt;

&lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;Service&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="s"&gt;Type=oneshot&lt;/span&gt;
&lt;span class="s"&gt;RemainAfterExit=yes&lt;/span&gt;
&lt;span class="s"&gt;Environment=PATH=%h/.local/bin:/usr/local/bin:/usr/bin:/bin&lt;/span&gt;
&lt;span class="s"&gt;ExecStop=%h/nemo-ops/bin/sandbox-clean-stop.sh&lt;/span&gt;
&lt;span class="s"&gt;TimeoutStopSec=180&lt;/span&gt;

&lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;Install&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="c1"&gt;# Pulled in by the gateway, not default.target: the gateway is After=default.target, so&lt;/span&gt;
&lt;span class="c1"&gt;# WantedBy=default.target forms an ordering cycle and systemd drops this unit's start job at boot (10-25 reboot).&lt;/span&gt;
&lt;span class="s"&gt;WantedBy=nemoclaw-openshell-gateway.service&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Result
&lt;/h2&gt;

&lt;p&gt;After applying the DB fix, the sandbox returned to &lt;code&gt;Ready&lt;/code&gt; at 13:21:53. All management functions, including &lt;code&gt;exec&lt;/code&gt;, health checks, Telegram notifications, and backups, resumed normal operation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;When management tools fail, use &lt;code&gt;docker exec&lt;/code&gt; to rescue data immediately since snapshots might not cover all paths.&lt;/li&gt;
&lt;li&gt;In older versions of software, a terminal error state can sometimes be bypassed by manually patching the underlying database if a backup is available.&lt;/li&gt;
&lt;li&gt;Avoid circular dependencies in systemd by ensuring &lt;code&gt;WantedBy&lt;/code&gt; targets do not create ordering loops with &lt;code&gt;After=&lt;/code&gt; requirements.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Still open
&lt;/h2&gt;

&lt;p&gt;NemoClaw remains pinned to OpenShell 0.0.116, meaning the underlying bug persists and requires the DB fix tool if a non-graceful shutdown occurs.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This post was written by a local Gemma 4 model from my own commit log and experiment notes, fact-checked against them by a local Qwen3.6 model, with no human edits.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>nvidia</category>
      <category>llm</category>
      <category>selfhosted</category>
      <category>devops</category>
    </item>
    <item>
      <title>[Hermes Agent] Fixing Voice Memo Skill Misidentification via pre_gateway_dispatch</title>
      <dc:creator>Pandas Studio</dc:creator>
      <pubDate>Sun, 11 Oct 2026 14:24:35 +0000</pubDate>
      <link>https://dev.to/pandas_studio_d473619e281/hermes-agent-fixing-voice-memo-skill-misidentification-via-pregatewaydispatch-4lhk</link>
      <guid>https://dev.to/pandas_studio_d473619e281/hermes-agent-fixing-voice-memo-skill-misidentification-via-pregatewaydispatch-4lhk</guid>
      <description>&lt;p&gt;While testing voice-to-document automation on a DGX Spark, the Hermes Agent repeatedly failed to trigger the intended 'voice-memo' skill. I discovered the model was misinterpreting the trigger word as a request to use its internal memory tool instead of the specialized skill.&lt;/p&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;The setup involved a Hermes Agent (v0.20.6) running on an NVIDIA DGX Spark with a Qwen 3.6-35B model. The goal was to send voice memos via Telegram, have them transcribed by a local Whisper server, and then have the agent transform them into structured documents saved to &lt;code&gt;/sandbox/notes/memos/&lt;/code&gt; using a specific skill.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happened
&lt;/h2&gt;

&lt;p&gt;During the second round of testing, despite successful transcriptions, the skill activation rate was 0/3. While the transcriptions correctly started with the word &lt;code&gt;메모&lt;/code&gt; ("memo"), the Qwen model consistently chose the default &lt;code&gt;memory&lt;/code&gt; tool to store the information rather than calling the &lt;code&gt;voice-memo&lt;/code&gt; skill. This resulted in the notes being saved to the general &lt;code&gt;MEMORY.md&lt;/code&gt; file instead of being processed into structured markdown files.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I tried
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Modifying the skill activation condition to trigger when the transcription starts with the word &lt;code&gt;메모&lt;/code&gt;.&lt;/strong&gt; → Failed; the model still prioritized the &lt;code&gt;memory&lt;/code&gt; tool over the specialized skill.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Adding technical term prompts to the Whisper STT engine to reduce transcription errors.&lt;/strong&gt; → Partially successful; reduced errors from 6 to 3 in specific test cases.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Root cause
&lt;/h2&gt;

&lt;p&gt;The root cause was a model-level semantic misunderstanding. When the agent received a message starting with &lt;code&gt;메모&lt;/code&gt;, the Qwen model interpreted the intent as 'remember this' and invoked its built-in &lt;code&gt;memory&lt;/code&gt; tool. Because the Hermes v0.20.6 gateway only passes the transcribed text as a plain quoted string without specific voice markers, the model had no way to distinguish a general conversational mention of 'memo' from a formal command to trigger the voice-memo skill.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix
&lt;/h2&gt;

&lt;p&gt;I implemented a custom Hermes plugin using the &lt;code&gt;pre_gateway_dispatch&lt;/code&gt; hook. This hook intercepts the message before it reaches the model. For audio, it performs a preliminary transcription (approx. 0.7s) to check if the first word is &lt;code&gt;메모&lt;/code&gt;. For text, it checks if the message starts with &lt;code&gt;메모:&lt;/code&gt;. If a match is found, the plugin rewrites the message to include a hardcoded instruction: &lt;code&gt;[voice-memo] This message is the author's memo... Do not use the memory tool... Call skill_view with name "voice-memo"...&lt;/code&gt;. This forces the model to use the correct skill by explicitly forbidding the &lt;code&gt;memory&lt;/code&gt; tool.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Voice memo plugin logic for intercepting and rewriting messages&lt;/em&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;SPOKEN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;^\W*메모(?=[\s,.:!?~…]|$)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# 전사는 "메모! 어제…", "메모 주간…"처럼 온다
&lt;/span&gt;&lt;span class="n"&gt;TYPED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;^\s*메모\s*[:：]&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;INSTRUCTION&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;[voice-memo] This message is the author&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s memo (it starts with the word 메모). &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
               &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Do not use the memory tool and do not continue earlier topics. &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
               &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Call skill_view with name &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;voice-memo&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt; and follow it exactly: write the document, save it with the file tool &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
               &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;under /sandbox/notes/memos/, then reply with the full document and the 저장: line.&lt;/span&gt;&lt;span class="sh"&gt;"&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;on_dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&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;**&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;kind&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message_type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;value&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="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&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="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;kind&lt;/span&gt; &lt;span class="o"&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="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;TYPED&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;action&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;rewrite&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;text&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="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;INSTRUCTION&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;text&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;span class="n"&gt;media&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;media_urls&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;kind&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;voice&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;audio&lt;/span&gt;&lt;span class="sh"&gt;"&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;media&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;SPOKEN&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;_transcribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;media&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="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()):&lt;/span&gt;
            &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;voice_memo: 메모 음성 → voice-memo 스킬 지시&lt;/span&gt;&lt;span class="sh"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;action&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;rewrite&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;text&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="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;INSTRUCTION&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;INSTRUCTION&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="c1"&gt;# 훅이 실패해도 메시지는 평소대로 간다
&lt;/span&gt;        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;voice_memo: 판단 실패 %s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Registering the hook in the plugin&lt;/em&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register_hook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pre_gateway_dispatch&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;on_dispatch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Result
&lt;/h2&gt;

&lt;p&gt;In the third round of testing, the fix was successful, achieving a 3/3 success rate for document saving. The &lt;code&gt;memory&lt;/code&gt; tool was used 0 times, and all notes were correctly processed into the target directory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;When an LLM consistently misinterprets a command as a different tool, do not rely on prompting alone; use a pre-dispatch hook to inject explicit constraints.&lt;/li&gt;
&lt;li&gt;For voice-driven automation, performing a lightweight preliminary transcription at the gateway level can provide the necessary context to steer model behavior.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Still open
&lt;/h2&gt;

&lt;p&gt;The system still experiences interrupted turns if a new voice message arrives while the agent is busy (&lt;code&gt;busy_input_mode: interrupt&lt;/code&gt;), and some transcription hallucinations (e.g., Whisper adding 'Thank you' at the end of silence) persist.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This post was written by a local Gemma 4 model from my own commit log and experiment notes, fact-checked against them by a local Qwen3.6 model, with no human edits.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>nvidia</category>
      <category>llm</category>
      <category>selfhosted</category>
      <category>devops</category>
    </item>
    <item>
      <title>[Hermes Agent] Solving Voice Memo Skill Trigger Failures via Plugin Hooks</title>
      <dc:creator>Pandas Studio</dc:creator>
      <pubDate>Thu, 08 Oct 2026 23:35:02 +0000</pubDate>
      <link>https://dev.to/pandas_studio_d473619e281/solving-voice-memo-skill-trigger-failures-via-plugin-hooks-4ldo</link>
      <guid>https://dev.to/pandas_studio_d473619e281/solving-voice-memo-skill-trigger-failures-via-plugin-hooks-4ldo</guid>
      <description>&lt;p&gt;While testing a voice-to-structured-document workflow on an NVIDIA DGX Spark, the Hermes Agent consistently failed to trigger the specific &lt;code&gt;voice-memo&lt;/code&gt; skill, opting for the default &lt;code&gt;memory&lt;/code&gt; tool instead. This post details how I implemented custom gateway hooks to force correct skill invocation and output validation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;The setup involved a Telegram-based voice interface connected to a Hermes Agent (v0.20.6) running on a DGX Spark. I used a host-side &lt;code&gt;whisper.cpp&lt;/code&gt; server with a &lt;code&gt;ggml-large-v3-turbo-q5_0&lt;/code&gt; model for STT (Speech-to-Text). The goal was to have the agent recognize a voice memo, transform it into a structured Markdown document using the &lt;code&gt;voice-memo&lt;/code&gt; skill, and save it to &lt;code&gt;/sandbox/notes/memos/&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happened
&lt;/h2&gt;

&lt;p&gt;In the first round of testing, transcription succeeded 5/5 times, but the &lt;code&gt;voice-memo&lt;/code&gt; skill was triggered 0/5 times. In the second round, after changing the trigger word to &lt;code&gt;메모&lt;/code&gt; (&lt;/p&gt;

&lt;h2&gt;
  
  
  What I tried
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Changing the skill trigger condition in the SKILL.md file to look for the word &lt;code&gt;메모&lt;/code&gt; (spoken) or &lt;code&gt;메모:&lt;/code&gt; (typed).&lt;/strong&gt; → The skill still failed to trigger 0/3 times; the Qwen model interpreted &lt;code&gt;메모&lt;/code&gt; as a command to use the default &lt;code&gt;memory&lt;/code&gt; tool rather than the specific skill.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Adding a technical terminology list to the Whisper STT prompt to reduce transcription errors.&lt;/strong&gt; → Transcription errors for technical terms dropped from 6 to 3 in subsequent tests.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Root cause
&lt;/h2&gt;

&lt;p&gt;The Hermes v0.20.6 gateway only passes transcribed text as a quoted line without any special markers if transcription is successful. The original skill relied on a &lt;code&gt;[The user sent a voice message ...]&lt;/code&gt; marker that only appeared during transcription failures. Even when I updated the skill to look for the word &lt;code&gt;메모&lt;/code&gt;, the LLM (Qwen) frequently misidentified the intent as a request to use the standard &lt;code&gt;memory&lt;/code&gt; tool for general storage.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix
&lt;/h2&gt;

&lt;p&gt;I implemented a custom plugin using two specific Hermes hooks. First, I used &lt;code&gt;pre_gateway_dispatch&lt;/code&gt; to intercept messages before they reached the model. For audio, the hook performs its own transcription (taking ~0.7s) to check if the first word is &lt;code&gt;메모&lt;/code&gt;. If it matches, it rewrites the message to include a hard instruction: &lt;code&gt;[voice-memo] This message is the author's memo... Do not use the memory tool...&lt;/code&gt;. Second, I added a &lt;code&gt;transform_llm_output&lt;/code&gt; hook to intercept the model's response, read the newly saved file, and replace the model's chatty reply with the full document content plus an automated quality check.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Plugin logic for pre-gateway dispatch&lt;/em&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;kind&lt;/span&gt; &lt;span class="o"&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="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;TYPED&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;action&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;rewrite&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;text&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="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;INSTRUCTION&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;text&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;span class="n"&gt;media&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;media_urls&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;kind&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;voice&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;audio&lt;/span&gt;&lt;span class="sh"&gt;"&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;media&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;SPOKEN&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;_transcribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;media&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="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()):&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;voice_memo: 메모 음성 → voice-memo 스킬 지시&lt;/span&gt;&lt;span class="sh"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;action&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;rewrite&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;text&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="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;INSTRUCTION&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;INSTRUCTION&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Automated document quality check logic&lt;/em&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;doc&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="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&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="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; 칸 없음&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;SECTIONS&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;_section&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;is&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;summary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;_section&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;정리&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;
    &lt;span class="n"&gt;missing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ITEMS&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;rf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;^\s*[-*]\s*\**&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;escape&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;\**\s*[:：]&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;M&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;summary&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;missing&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="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;정리에 빠진 항목: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;missing&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nf"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cjk&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FOREIGN&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;doc&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="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;한자·가나 섞임: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cjk&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Result
&lt;/h2&gt;

&lt;p&gt;In the third round of testing, the system achieved 3/3 successful saves and 0 uses of the &lt;code&gt;memory&lt;/code&gt; tool. The automated check successfully identified missing sections, the presence of Chinese/Japanese characters, and English words used outside the original context.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Don't rely on LLM intent recognition for critical tool selection; use gateway hooks to inject explicit instructions.&lt;/li&gt;
&lt;li&gt;When building voice workflows, intercepting the stream before the gateway can provide much more reliable trigger detection.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Still open
&lt;/h2&gt;

&lt;p&gt;The system still experiences 'interrupted' turns if a new voice message arrives while the agent is still processing a previous one due to the &lt;code&gt;busy_input_mode: interrupt&lt;/code&gt; setting.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This post was written by a local Gemma 4 model from my own commit log and experiment notes, fact-checked against them by a local Qwen3.6 model, with no human edits.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>nvidia</category>
      <category>llm</category>
      <category>selfhosted</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
