<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Hermes Studio on 大卷学长的博客</title><link>https://blog.liurb.org/tags/hermes-studio/</link><description>Recent content in Hermes Studio on 大卷学长的博客</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><lastBuildDate>Mon, 07 Sep 2026 12:00:00 +0000</lastBuildDate><atom:link href="https://blog.liurb.org/tags/hermes-studio/index.xml" rel="self" type="application/rss+xml"/><item><title>Hermes Agent 桌面端 Runtime 配置实战：踩坑与修复全记录</title><link>https://blog.liurb.org/posts/2026/09/07/hermes_agent_setup/</link><pubDate>Mon, 07 Sep 2026 12:00:00 +0000</pubDate><guid>https://blog.liurb.org/posts/2026/09/07/hermes_agent_setup/</guid><description>&lt;img src="https://blog.liurb.org/images/posts/2026/hermes_agent_setup_logo.png" alt="Featured image of post Hermes Agent 桌面端 Runtime 配置实战：踩坑与修复全记录" />&lt;p>最近把 &lt;strong>hermes-agent&lt;/strong>（Nous Research 出的那个跑在终端里的 agent）装到 Windows 上，又折腾了一套桌面客户端 &lt;strong>Hermes Studio&lt;/strong> 的 runtime 配置。整个过程不算短，中间踩了几个挺典型的坑——模块导不进去、编译二进制不拾取源码、还有一次挺绕的 WAF 403。整理成一篇记录，给同样在 Windows 上折腾 hermes 的朋友做个参考。&lt;/p>
&lt;p>先交代一下背景。&lt;strong>Hermes Agent&lt;/strong> 是个模型无关、能调工具的 AI agent，可以本地跑也可以扔 VPS 上；&lt;strong>Hermes Studio&lt;/strong> 则是它的桌面端 + Web 控制台，负责跑 agent 对话、管理模型和 profile、看日志之类的。桌面端跟纯 CLI 不一样的是，它需要一套独立的 runtime 目录来调用 CLI 命令，这套东西配起来才是真正费劲的地方。&lt;/p>
&lt;h2 id="一环境要求">一、环境要求
&lt;/h2>&lt;p>我的环境大概是这么个组合：&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>组件&lt;/th>
&lt;th>版本&lt;/th>
&lt;th>说明&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Python&lt;/td>
&lt;td>3.11.x&lt;/td>
&lt;td>用 uv 管理，&lt;code>pyproject.toml&lt;/code> 指定 &lt;code>&amp;gt;=3.11,&amp;lt;3.14&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Node.js&lt;/td>
&lt;td>v26.8.1&lt;/td>
&lt;td>用 fnm 管理，桌面端 UI 需要&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Git&lt;/td>
&lt;td>自带&lt;/td>
&lt;td>Windows 桌面 runtime 需要&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>uv&lt;/td>
&lt;td>最新版&lt;/td>
&lt;td>Python 包管理器&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>fnm&lt;/td>
&lt;td>最新版&lt;/td>
&lt;td>Node.js 版本管理&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>注意 Python 版本有硬性要求，太新太旧都不行。我这边用 uv 管 Python，fnm 管 Node，两条线分开走，互不干扰。&lt;/p>
&lt;h2 id="二项目安装">二、项目安装
&lt;/h2>&lt;h3 id="21-创建虚拟环境">2.1 创建虚拟环境
&lt;/h3>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 如果 .venv 不存在，uv 会自动创建&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">uv sync
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;strong>注意&lt;/strong>：&lt;code>uv sync&lt;/code> 这里大概率会翻车。原因在 &lt;code>pyproject.toml&lt;/code> 里的 &lt;code>exclude-newer = &amp;quot;14 days&amp;quot;&lt;/code> 这条规则。&lt;/p>
&lt;blockquote>
&lt;p>&lt;code>exclude-newer&lt;/code> 是 uv 的&amp;quot;依赖冷却期&amp;quot;机制，用来做供应链安全——它会把指定时间窗口内新发布的包全部忽略掉。问题是有些 PyPI 包没带上传日期，被 uv 当成&amp;quot;过期&amp;quot;过滤掉，解析就挂了。&lt;/p>&lt;/blockquote>
&lt;p>这个规则本身是个好设计，但在实际项目里偶尔会误伤。解决办法很简单，绕开 uv 直接走 pip：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">source&lt;/span> .venv/bin/activate &lt;span class="c1"># 或 .venv\Scripts\activate&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">pip install -e &lt;span class="s2">&amp;#34;.[dev]&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>不想每次手动改的话，也可以把冷却期临时改短，或者用 &lt;code>exclude-newer-package&lt;/code> 针对单个包豁免。不过对我这种只想快速跑起来的人来说，&lt;code>pip install -e &amp;quot;.[dev]&amp;quot;&lt;/code> 是最省事的。&lt;/p>
&lt;h3 id="22-安装-nodejs-依赖">2.2 安装 Node.js 依赖
&lt;/h3>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">fnm use 26.8.1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">npm install
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Node 这块就没什么坑了，装完就完事。&lt;/p>
&lt;h2 id="三桌面客户端-runtime-配置">三、桌面客户端 Runtime 配置
&lt;/h2>&lt;p>Hermes Studio 桌面客户端需要一个独立的 runtime 目录来运行 CLI 命令，结构大概是这样的：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">~/.hermes-web-ui/desktop-runtime/hermes/&amp;lt;version&amp;gt;/&amp;lt;platform&amp;gt;/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── python/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ ├── python.exe # Python 解释器
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ ├── Scripts/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ │ ├── hermes.cmd # 启动批处理文件
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ │ └── hermes.exe # Hermes CLI 编译二进制
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ ├── run_agent.py # Agent 入口
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ ├── cli.py # CLI 入口
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ └── venv/ # (可选) venv 环境
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── node/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ └── node.exe # Node.js 运行时
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── git/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ └── cmd/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ └── git.exe # Git 命令行工具
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── runtime-manifest.json # Runtime 元数据
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└── active-version.json # 当前激活版本信息
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这里我把所有本机真实路径都用占位符替代了（&lt;code>%HERMES_REPO%&lt;/code> 指仓库路径，&lt;code>%USERPROFILE%&lt;/code> 指用户主目录），免得以后自己翻记录的时候路径对不上。&lt;/p>
&lt;h3 id="31-复制运行时组件">3.1 复制运行时组件
&lt;/h3>&lt;p>把项目 &lt;code>.venv&lt;/code> 里的东西复制到 runtime 目录，核心是这几样：&lt;/p>
&lt;ul>
&lt;li>&lt;code>.venv\Scripts\python.exe&lt;/code> → &lt;code>python\python.exe&lt;/code>&lt;/li>
&lt;li>&lt;code>.venv\Scripts\hermes.cmd&lt;/code> → &lt;code>python\Scripts\hermes.cmd&lt;/code>&lt;/li>
&lt;li>&lt;code>.venv\Scripts\hermes.exe&lt;/code> → &lt;code>python\Scripts\hermes.exe&lt;/code>&lt;/li>
&lt;li>&lt;code>run_agent.py&lt;/code>、&lt;code>cli.py&lt;/code> → &lt;code>python\run_agent.py&lt;/code>、&lt;code>python\cli.py&lt;/code>&lt;/li>
&lt;li>fnm 的 &lt;code>node.exe&lt;/code> → &lt;code>node\node.exe&lt;/code>&lt;/li>
&lt;li>Git 的 &lt;code>git.exe&lt;/code> + 依赖 DLL → &lt;code>git\cmd\git.exe&lt;/code>&lt;/li>
&lt;/ul>
&lt;h3 id="32-创建-runtime-元数据文件">3.2 创建 runtime 元数据文件
&lt;/h3>&lt;p>&lt;code>runtime-manifest.json&lt;/code> 和 &lt;code>active-version.json&lt;/code> 是 Studio 判断 runtime 就绪的钥匙，格式如下：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;schema&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;platform&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;win-x64&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;span class="lnt">9
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;schema&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;desktopAppVersion&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;0.20.6&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;hermesRuntimeVersion&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;0.21.1&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;runtimeDirectory&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;%USERPROFILE%\\.hermes-web-ui\\desktop-runtime\\hermes\\0.20.6\\win-x64&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;platform&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;win-x64&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;updatedAt&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;2026-09-09T00:00:00.000Z&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;runtimeValidationFailures&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Studio 的 &lt;code>runtime-manager.js&lt;/code> 会逐个检查必需文件（&lt;code>python.exe&lt;/code>、&lt;code>hermes.cmd&lt;/code>、&lt;code>node.exe&lt;/code>、&lt;code>run_agent.py&lt;/code>、&lt;code>cli.py&lt;/code>、&lt;code>git.exe&lt;/code> 等）是否齐全，缺一个就报 runtime error。&lt;/p>
&lt;h2 id="四关键问题与修复">四、关键问题与修复
&lt;/h2>&lt;p>这一节才是这次折腾的主菜，三个坑都挺有代表性。&lt;/p>
&lt;h3 id="41-坑一modulenotfounderror-no-module-named-hermes_cli">4.1 坑一：ModuleNotFoundError: No module named &amp;lsquo;hermes_cli&amp;rsquo;
&lt;/h3>&lt;p>配置完 runtime，一跑就报：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">ModuleNotFoundError: No module named &amp;#39;hermes_cli&amp;#39;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;strong>原因&lt;/strong>：&lt;code>.venv\Scripts\python.exe&lt;/code> 本质上是个 venv 启动器。当它从 runtime 目录（而不是项目目录）被调用时，&lt;code>sys.path&lt;/code> 里只有 uv 管理的 Python 默认路径，项目源码和 venv 的 &lt;code>site-packages&lt;/code> 都不在——于是什么都导入不了。&lt;/p>
&lt;p>&lt;strong>修复&lt;/strong>：在 uv 基础 Python 的 &lt;code>site-packages&lt;/code> 里放一个 &lt;code>sitecustomize.py&lt;/code>，让 Python 启动时自动把项目路径和 venv 的 site-packages 塞进 &lt;code>sys.path&lt;/code>：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">sys&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">sys&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">insert&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="sa">r&lt;/span>&lt;span class="s2">&amp;#34;%HERMES_REPO%&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">sys&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">insert&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="sa">r&lt;/span>&lt;span class="s2">&amp;#34;%HERMES_REPO%\.venv\Lib\site-packages&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;blockquote>
&lt;p>这里用到的机制是 Python 的 &lt;code>sitecustomize.py&lt;/code>——每个 Python 进程启动时，&lt;code>site&lt;/code> 模块会自动 import 它，所以放这里等于给所有走这个解释器的命令统一打补丁。一个文件解决 &lt;code>hermes_cli&lt;/code> 和后面的 &lt;code>yaml&lt;/code> 两个问题。&lt;/p>&lt;/blockquote>
&lt;h3 id="42-坑二modulenotfounderror-no-module-named-yaml">4.2 坑二：ModuleNotFoundError: No module named &amp;lsquo;yaml&amp;rsquo;
&lt;/h3>&lt;p>和上一个同根同源——runtime Python 访问不到 venv 里的第三方包（PyYAML、pydantic 这些）。&lt;code>sitecustomize.py&lt;/code> 里加了 &lt;code>.venv\Lib\site-packages&lt;/code> 路径后，这个坑自动填平了。&lt;/p>
&lt;h3 id="43-坑三编译二进制不拾取源码修改">4.3 坑三：编译二进制不拾取源码修改
&lt;/h3>&lt;p>这个坑最隐蔽。&lt;code>.venv\Scripts\hermes.exe&lt;/code> 是个编译后的二进制（108KB），在 &lt;code>pip install -e &amp;quot;.[dev]&amp;quot;&lt;/code> 时它把当时的源码打包进去了。你改了 &lt;code>agent/auxiliary_client.py&lt;/code> 之类的源文件，&lt;code>hermes.exe&lt;/code> 还是跑编译时的旧代码——&lt;strong>源码改了，行为不变&lt;/strong>，排查半天才发现问题在这。&lt;/p>
&lt;p>&lt;strong>修复&lt;/strong>：把所有 &lt;code>.cmd&lt;/code>/&lt;code>.bat&lt;/code> 包装器改成用 &lt;code>python.exe -m&lt;/code> 直接跑源码，绕开编译二进制：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-batch" data-lang="batch">&lt;span class="line">&lt;span class="cl">&lt;span class="p">@&lt;/span>&lt;span class="k">echo&lt;/span> off
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">set&lt;/span> &lt;span class="nv">PYTHONPATH&lt;/span>&lt;span class="p">=&lt;/span>&lt;span class="nv">%HERMES_REPO%&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">%HERMES_REPO%&lt;/span>\.venv\Scripts\python.exe -m hermes_cli.main &lt;span class="nv">%*&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>受影响的是 runtime 目录下的 &lt;code>hermes.cmd&lt;/code>、&lt;code>hermes-agent.cmd&lt;/code>、&lt;code>hermes-acp.cmd&lt;/code>，以及 &lt;code>%USERPROFILE%\bin&lt;/code> 下那几个 &lt;code>.bat&lt;/code>。改完之后，源码改动的确即时生效，不再有&amp;quot;改了没反应&amp;quot;的困惑。&lt;/p>
&lt;blockquote>
&lt;p>这是很多 Python CLI 项目在 Windows 上共同的坑：&lt;code>pip install -e&lt;/code> 生成的 &lt;code>.exe&lt;/code> 是入口打包器，不是源码软链。以后改了源码不生效，第一反应先查是不是走在了编译二进制上。&lt;/p>&lt;/blockquote>
&lt;h2 id="五系统环境变量配置">五、系统环境变量配置
&lt;/h2>&lt;p>为了让 &lt;code>hermes&lt;/code>、&lt;code>hermes-agent&lt;/code>、&lt;code>hermes-acp&lt;/code> 命令在任意终端都能直接敲，我建了一组包装脚本放到 &lt;code>%USERPROFILE%\bin\&lt;/code>，并把这个目录加进了 PATH。&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>文件&lt;/th>
&lt;th>内容&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>%USERPROFILE%\bin\hermes.bat&lt;/code>&lt;/td>
&lt;td>&lt;code>@echo off&lt;/code> + &lt;code>python.exe -m hermes_cli.main %*&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>%USERPROFILE%\bin\hermes-agent.bat&lt;/code>&lt;/td>
&lt;td>&lt;code>@echo off&lt;/code> + &lt;code>python.exe -m hermes_cli.main %*&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>%USERPROFILE%\bin\hermes-acp.bat&lt;/code>&lt;/td>
&lt;td>&lt;code>@echo off&lt;/code> + &lt;code>python.exe -m hermes_cli.acp %*&lt;/code>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>验证方式很直接，任意目录敲 &lt;code>hermes --version&lt;/code>，能输出 &lt;code>Hermes Agent v0.21.x&lt;/code> 就对了。&lt;/p>
&lt;h2 id="六验证清单">六、验证清单
&lt;/h2>&lt;p>配完后逐项过一遍，确认没漏：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="line">&lt;span class="cl">&lt;span class="c"># 1. Python 模块导入&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">python&lt;/span> &lt;span class="n">-c&lt;/span> &lt;span class="s2">&amp;#34;import hermes_cli; print(&amp;#39;hermes_cli OK&amp;#39;)&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">python&lt;/span> &lt;span class="n">-c&lt;/span> &lt;span class="s2">&amp;#34;import yaml; print(&amp;#39;yaml OK&amp;#39;)&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c"># 2. hermes CLI 版本&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">python&lt;/span>&lt;span class="p">\&lt;/span>&lt;span class="n">Scripts&lt;/span>&lt;span class="p">\&lt;/span>&lt;span class="n">hermes&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="py">cmd&lt;/span> &lt;span class="p">-&lt;/span>&lt;span class="n">-version&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c"># 期望输出: Hermes Agent v0.21.x&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c"># 3. logs list 命令&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">python&lt;/span> &lt;span class="n">-m&lt;/span> &lt;span class="n">hermes_cli&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="py">main&lt;/span> &lt;span class="n">logs&lt;/span> &lt;span class="n">list&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c"># 期望输出日志文件列表，无报错&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c"># 4. runtime 文件完整性&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c"># 确认 python.exe / hermes.cmd / run_agent.py / node.exe / git.exe 等文件均存在&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="七版本升级流程">七、版本升级流程
&lt;/h2>&lt;p>hermes-agent 仓库发新版时（比如 0.21.0 → 0.21.1），升级 runtime 其实很轻量：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">git fetch origin
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">git checkout v&amp;lt;new-version&amp;gt;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">pip install -e &lt;span class="s2">&amp;#34;.[dev]&amp;#34;&lt;/span> --no-deps --no-build-isolation
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>由于 runtime 通过 &lt;code>sitecustomize.py&lt;/code> 指向仓库路径，&lt;strong>代码自动更新，基本不用重新复制 runtime 文件&lt;/strong>，只要把 &lt;code>active-version.json&lt;/code> 里的版本号更新一下就行。&lt;/p>
&lt;h2 id="八waf-403-排查实录">八、WAF 403 排查实录
&lt;/h2>&lt;p>这节是压轴的，也是最烧脑的一个坑。Studio 通过一个托管在 PaaS 平台（Render 免费托管）上的自定义 OpenAI 兼容代理访问模型时，出现了一个非常诡异的现象：&lt;/p>
&lt;ul>
&lt;li>纯多轮聊天一切正常；&lt;/li>
&lt;li>一旦 agent 调用工具（terminal 输出、读文件等结果回填进消息），下一次请求就大概率 403；&lt;/li>
&lt;li>但同一端点、同一 API Key 用 OpenAI SDK 手工测试又完全正常。&lt;/li>
&lt;/ul>
&lt;p>一开始我按&amp;quot;请求指纹&amp;quot;的假设去查，走了不少弯路。依次排除过三类嫌疑：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>OpenAI SDK 的 &lt;code>x-stainless-*&lt;/code> 系列 headers&lt;/strong>（lang / package-version / os / arch 等）——在 httpx 请求层全部剥离后 403 依旧；&lt;/li>
&lt;li>&lt;strong>SDK 默认的 &lt;code>User-Agent: OpenAI/Python ...&lt;/code>&lt;/strong>——对 custom_providers 路由写死 UA 后 403 依旧；&lt;/li>
&lt;li>&lt;strong>第三方插件、tools、压缩链路里&amp;quot;裸构造 &lt;code>OpenAI(...)&lt;/code> 的路径&amp;quot;&lt;/strong>——做全局默认注入兜底后 403 依旧。&lt;/li>
&lt;/ol>
&lt;p>三步做完时，所有出站请求的指纹已经验证是干净的了（&lt;code>ua=... stainless=[]&lt;/code>），但 403 依然如故。&lt;/p>
&lt;h3 id="真正根因paas-平台-waf-对大请求包直接-blocked">真正根因：PaaS 平台 WAF 对大请求包直接 Blocked
&lt;/h3>&lt;p>最后才定位到：某些 PaaS 平台的边缘 &lt;strong>WAF&lt;/strong> 会对&lt;strong>大请求包&lt;/strong>、以及&lt;strong>包含特定特征内容&lt;/strong>的请求（比如 &lt;code>../&lt;/code> 路径序列、&lt;code>${jndi:&lt;/code> 这类字符串）直接返回 403，响应体是平台的 &lt;code>&amp;lt;title&amp;gt;Blocked&amp;lt;/title&amp;gt;&lt;/code> 拦截页，不是正常的 JSON 错误。&lt;/p>
&lt;p>这一下所有现象都解释通了：&lt;/p>
&lt;ul>
&lt;li>纯聊天的请求体只有几 KB、内容干净 → 正常通过；&lt;/li>
&lt;li>工具调用后，工具输出被回填进消息，请求体涨到几十~上百 KB，而且几乎必然包含路径等内容 → 命中 WAF 规则 → 403；&lt;/li>
&lt;li>&lt;strong>拦截的是请求 body，headers 层面怎么改都无解&lt;/strong>。&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>WAF 判断的是请求体内容（body），不是 header 指纹。工具输出一多、一长，body 里就难免出现 &lt;code>../&lt;/code> 这类触发特征，于是被边缘 WAF 拦在门外——这类问题跟云厂商（Azure Application Gateway、Cloudflare、Fly Proxy 等）WAF 拦截合法请求的原理完全一致，都是内容规则命中，不是身份认证问题。&lt;/p>&lt;/blockquote>
&lt;h3 id="解法">解法
&lt;/h3>&lt;ul>
&lt;li>&lt;strong>根治：换托管&lt;/strong>。把自建代理迁出有内容 WAF 的免费托管，本地/内网直接跑、自有 VPS、或换没有这类规则的平台。&lt;/li>
&lt;li>&lt;strong>客户端缓解（降概率，无法根治）&lt;/strong>：更激进的上下文压缩、限制单条工具输出长度——但 body 里只要仍出现触发特征，照样被拦。&lt;/li>
&lt;li>&lt;strong>headers 加固对 403 无解，但建议保留&lt;/strong>当通用防御，顺便把诊断日志沉淀下来，方便下次排查。&lt;/li>
&lt;/ul>
&lt;p>诊断落点放在了 Studio runtime 的 &lt;code>sitecustomize.py&lt;/code> 里：启动指纹、httpx send 层加固（剥 &lt;code>x-stainless-*&lt;/code> + 对 custom_providers host 写死 UA）、requests/urllib 保底加固，以及全量出站诊断日志。日志写到真实数据目录下的 &lt;code>waf_diag.log&lt;/code>，用环境变量 &lt;code>HERMES_WAF_DIAG=0&lt;/code> 可关闭。&lt;/p>
&lt;p>最后收了个尾：仓库内的源码补丁全部 &lt;code>git checkout&lt;/code> 还原，上游源码保持干净，所有运行时加固只保留在 uv 的 &lt;code>sitecustomize.py&lt;/code> 一处。这样将来某条 custom_providers 路由需要注入 UA，只需要在该路由的 config 条目加 &lt;code>extra_headers&lt;/code> 就行，不用再动源码。&lt;/p>
&lt;h2 id="九维护注意事项">九、维护注意事项
&lt;/h2>&lt;p>几条容易再踩的，先记下来：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>sitecustomize.py&lt;/strong>：如果 uv Python 升级或 site-packages 位置变化，需要重新创建；&lt;/li>
&lt;li>&lt;strong>编译二进制陷阱&lt;/strong>：&lt;code>.venv/Scripts/hermes.exe&lt;/code> 是编译快照，不拾取源码修改。改源码后务必走 &lt;code>python.exe -m&lt;/code>，别用 &lt;code>hermes.exe&lt;/code>；&lt;/li>
&lt;li>&lt;strong>uv sync 问题&lt;/strong>：&lt;code>exclude-newer&lt;/code> 卡住时，用 &lt;code>pip install -e &amp;quot;.[dev]&amp;quot;&lt;/code> 临时绕过；&lt;/li>
&lt;li>&lt;strong>MCP SDK 依赖&lt;/strong>：MCP server 需要 &lt;code>mcp==2.0.0&lt;/code> + &lt;code>pywin32&lt;/code>，装完还得跑 &lt;code>pywin32_postinstall.py -install&lt;/code> 把 DLL 放对位置；Desktop Python 的 &lt;code>sitecustomize.py&lt;/code> 要额外加 &lt;code>win32&lt;/code>、&lt;code>win32/lib&lt;/code>、&lt;code>Pythonwin&lt;/code> 路径（因为 &lt;code>.pth&lt;/code> 文件不被处理）；&lt;/li>
&lt;li>&lt;strong>HERMES_HOME&lt;/strong>：Windows 上 Hermes 默认 &lt;code>HERMES_HOME&lt;/code> 是 &lt;code>%LOCALAPPDATA%\hermes&lt;/code>，不是 &lt;code>~/.hermes/&lt;/code>。改了 &lt;code>~/.hermes/config.yaml&lt;/code> 但 CLI 读不到，得复制过去或设环境变量。&lt;/li>
&lt;/ol>
&lt;h2 id="十总结">十、总结
&lt;/h2>&lt;p>这次配置的核心收获就三条：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>&lt;code>sitecustomize.py&lt;/code> 是 Windows 上修 Python 路径问题的万能钥匙&lt;/strong>——一个文件，让所有走这个解释器的命令都能拿到正确的 &lt;code>sys.path&lt;/code>；&lt;/li>
&lt;li>&lt;strong>&lt;code>python.exe -m&lt;/code> 才是开发期的正解&lt;/strong>，别信编译二进制，那只是发布时的产物；&lt;/li>
&lt;li>&lt;strong>碰到&amp;quot;小请求没事、大请求 403&amp;quot;的怪象，先怀疑 body 内容被 WAF 拦&lt;/strong>，别在 header 指纹上死磕。&lt;/li>
&lt;/ul></description></item></channel></rss>