文窗口太贵技能越多越不能一次性把所有细节暴露给模型。Hermes 选择了一条更朴素的路运行时扫描分层披露只有在真正用到某个技能时才付出更多 token。inline-01.jpg图模型先看到技能地图而不是一次性读完整手册Skill Runtime 的基本边界主目录、外部目录与命名空间Hermes 把 ~/.hermes/skills/ 视为技能的主目录。它是默认读写位置也是本地技能的单一真实来源。外部目录通过 external_dirs 接入更多承担扩展和共享的角色默认不会改变本地主目录的权威性。典型目录长这样~/.hermes/skills/├── category/│ └── skill-name/│ └── SKILL.md├── .hub/│ ├── lock.json│ ├── quarantine/│ └── audit.log├── .bundled_manifest└── .archive/这里有几个工程取舍。SKILL.md 是技能入口.hub 保存 Hub 相关的本地状态.archive 和 .bundled_manifest 属于维护层数据不应该参与正常技能扫描。Hermes 在遍历时会主动排除这些目录也会跳过 .git、node_modules、虚拟环境、缓存目录等高噪声路径。EXCLUDED_SKILL_DIRS frozenset((“.git”, “.github”, “.hub”, “.archive”,“.venv”, “venv”, “node_modules”, “site-packages”,“pycache”, “.tox”, “.nox”, “.pytest_cache”,“.mypy_cache”, “.ruff_cache”))命名空间是另一条边界。普通技能用 skill-name 解析插件技能用 namespace:skill 解析。这个冒号并不是装饰它告诉运行时先拆出插件命名空间再去插件目录里找对应技能。没有这个规则插件生态很快会撞名。def parse_qualified_name(name: str) - Tuple[Optional[str], str]:if “:” not in name:return None, namereturn tuple(name.split(“:”, 1))本地优先也很关键。同名技能出现时Hermes 不会让外部目录悄悄覆盖用户本地版本真正发生多候选冲突时它会返回明确错误和所有匹配路径让用户改用完整相对路径。这个决定减少了“为什么今天调用的不是昨天那个技能”的排查成本。Level 0skills_list 只给模型一张轻量地图会话启动时Hermes 不会把每个技能的正文都塞进上下文。skills_list() 只返回技能元数据大约是一个固定的 token 成本。原文估算 Level 0 约为 3k tokens这笔成本会在会话启动时支付后面的完整技能内容和参考文件只有在触发时才成为边际成本。Level 0: skills_list() - 仅技能元数据约 3k tokensLevel 1: skill_view(name) - 完整 SKILL.md通常 10-100k tokensLevel 2: skill_view(name, path) - 技能目录内的某个参考文件Level 0 的目标不是“让模型读懂所有技能”而是让模型知道有哪些技能、每个技能大概负责什么、是否适合当前平台和环境。它更像一张地图不是一本手册。mermaid-01.png图Level 0 从目录扫描到条件激活过滤的完整链路返回值只保留必要字段{“name”: “skill-name”,“description”: “Brief description…”,“category”: “category-name”,“tags”: [“tag1”, “tag2”]}这个阶段的扫描没有依赖 SQLite 或预构建 JSON 索引。Hermes 每次执行 skills_list() 都会重新扫文件系统def iter_skill_index_files(skills_dir: Path, filename: str):for root, dirs, files in os.walk(skills_dir, followlinksTrue):dirs[:] [d for d in dirs if d not in EXCLUDED_SKILL_DIRS]if filename in files:yield Path(root) / filenamedirs[:] 的原地修改是一个小但实用的优化。它不是在遍历后过滤结果而是在 os.walk() 继续递归之前剪掉不该进入的目录。技能数量少于千级时这种按需扫描足够简单维护成本也比额外索引低。Level 0 还会处理平台和环境匹配。platforms 为空时默认通过macos 会映射到 darwinlinux、windows 也有对应映射。Termux 需要特殊处理因为它跑在 Android 上却经常要兼容 Linux 技能。PLATFORM_MAP {“macos”: “darwin”,“linux”: “linux”,“windows”: “win32”}环境字段也在这个阶段过滤。Hermes 内置识别 kanban、docker、s6未知环境默认通过。这个默认值有点宽松但它避免了一个更糟的问题新环境还没被运行时认识时技能全部消失。_KNOWN_ENVIRONMENTS frozenset({“kanban”, “docker”, “s6”})条件激活是 Level 0 里更像 Agent 的部分。技能可以声明 requires_toolsets、requires_tools也可以声明 fallback_for_toolsets、fallback_for_tools。前者表示依赖不可用就隐藏后者表示主工具可用时自己退场。比如一个 DuckDuckGo 搜索技能可以只在正式 Web 工具不可用时出现。mermaid-02.png图依赖工具与 fallback 工具共同决定技能是否显示这样做的好处是模型看到的不是“所有可能工具”而是当前会话真正有意义的工具。对 Agent 来说这比单纯减少 token 更重要因为候选越乱错误调用的概率越高。Level 1skill_view 的难点在名字解析而不是读文件当模型决定使用某个技能时才进入 Level 1。skill_view(name) 会读取完整 SKILL.md执行必要的前置处理然后把完整技能说明交给模型。mermaid-03.png图Level 1 的命名解析、冲突检查与内容预处理四层名称解析策略体现了兼容性优先级策略 1直接路径direct_path search_dir / nameif direct_path.is_dir() and (direct_path / “SKILL.md”).exists():return direct_path / “SKILL.md”策略 2递归按目录名匹配for found_skill_md in iter_skill_index_files(search_dir, “SKILL.md”):if found_skill_md.parent.name name:return found_skill_md策略 3按 frontmatter 的 name 字段匹配fm, _ _parse_frontmatter(fm_content)if fm.get(“name”) name:return found_skill_md策略 4兼容旧式扁平 .md 文件for found_md in search_dir.rglob(f{name}.md):if found_md.name ! “SKILL.md”:return found_md直接路径优先说明 Hermes 鼓励用户在冲突时显式指定位置。目录名匹配符合大多数人的直觉frontmatter 名称匹配给重命名目录留下空间legacy .md 负责兼容旧技能。真正撞名时运行时不会装作没事if len(candidates) 1:return json.dumps({“success”: False,“error”: fAmbiguous skill name ‘{name}’: {len(candidates)} skills match,“matches”: [str(smd) for _, smd in candidates],“hint”: “Use full relative path instead”})这比“按某个顺序静默选第一个”可靠得多。技能是会执行命令、写文件、访问外部系统的名称解析上的模糊不该被吞掉。插件技能在 Level 1 里走一条相似但带命名空间的链路。skill_view(“plugin:skill”) 会先定位插件再找插件内的技能。如果插件存在但技能不存在运行时可以列出可用技能如果找到了会在返回内容前附加上下文横幅提醒模型这是哪个插件的一部分以及有哪些 sibling skills 可以用限定名调用。[Bundle context: This skill is part of the plugin plugin.Sibling skills: skill1, skill2.Use qualified form to invoke siblings (e.g. plugin:skill1).]Level 1 的后半段是技能内容预处理。Hermes 支持模板变量${HERMES_SKILL_DIR} - 当前技能目录的绝对路径${HERMES_SESSION_ID} - 当前会话 ID如果配置允许还可以执行内联 shellCurrent date: !date -u %Y-%m-%dGit branch: !git -C ${HERMES_SKILL_DIR} rev-parse --abbrev-ref HEAD这个能力很锋利所以它应该被视为受控扩展而不是普通 Markdown 特性。技能内容一旦能执行命令路径安全和注入检测就必须跟上。Hermes 对技能名做了绝对路径、Windows drive、… 路径穿越检查也内置了一批 prompt injection 模式例如 ignore previous instructions、system prompt:、。Hub 安装技能还有额外安全扫描重点检查数据渗出、破坏性命令、Shell 注入和 prompt 注入。它不能证明技能一定安全但能挡住一批低成本攻击。配置注入也发生在 Level 1。技能可以在 frontmatter 里声明自己需要的配置项metadata:hermes:config:- key: wiki.pathdescription: Path to wiki directorydefault: ~/wikiprompt: Wiki directory path运行时会从配置文件读取 skills.config.logical_key没有值就用默认值再展开 ~ 和环境变量。最后把结果追加到技能内容里[Skill config (from ~/.hermes/config.yaml):wiki.path /Users/erik/wiki]这里可以看出 Hermes 对“技能加载”和“技能管理”分得很清楚。加载是按需读取和注入管理则走 skill_manage支持 create、patch、edit、delete、write_file、remove_file。如果开启 skills.write_approval写入不会直接落盘而是进入 ~/.hermes/pending/skills/等待用户 review、diff、approve 或 reject。Level 2参考文件把长技能拆成可控切片inline-02.jpg图低频细节被延后到参考文件按需进入上下文很多技能的主文件不应该无限膨胀。Hermes 的 Level 2 允许按路径读取技能目录内的参考文件skill_view(“skill-name”, “references/api.md”)这一步解决的是另一个 token 问题有些知识只在特定分支下需要比如 API 细节、样式规范、平台适配说明。把这些内容全塞进 SKILL.md会让每次技能加载都变重拆到 references/ 以后主技能只负责路由和原则细节按需进入上下文。可以把三层加载理解成这张表层级 入口 模型拿到什么 主要成本 适合放什么Level 0 skills_list() 名称、描述、分类、标签 会话启动固定成本 技能发现与选择信号Level 1 skill_view(name) 完整 SKILL.md 使用技能时的边际成本 工作流、边界、关键规则Level 2 skill_view(name, path) 某个参考文件 分支触发成本 API 细节、模板、长规范原文尾部提到的性能优化、安全机制、平台环境匹配其实都应该放回这三层里看。Level 0 负责过滤和候选控制Level 1 负责解析、预处理和安全检查Level 2 负责把低频细节延后。这样理解后Hermes Skill System 就不是“技能列表 技能详情”的二段式接口而是一套围绕上下文窗口设计的运行时。一次完整调用从会话启动到技能内容进入上下文会话启动时Hermes 读取配置解析外部目录然后扫描技能元数据mermaid-04.png图会话启动时只注入精简技能元数据用户触发技能后完整内容才进入模型上下文mermaid-05.png图技能被触发后才读取完整说明进入上下文如果技能再请求 references/api.md才进入 Level 2。这个顺序让技能系统可以支持很多技能又不会在每次会话里一次性支付全部成本。