摘要

最近给自己用的 Hermes Agent 写了一个本地审计 dashboard,叫 Caduceus。两条相交的蛇 = skill 的 lineage(创建 / 补丁 / 覆写两条线交织展开),它做的事就是把那些被 agent 层层覆写的笔迹翻出来给你看。

Caduceus /kəˈdjuːsɪəs/ — Hermes 那柄两条蛇缠绕的赫尔墨斯杖。这个工具就是 Hermes Agent 的赫尔墨斯杖:你拿着它,就能看见 agent 在地下做了什么。

GitHub · InfiniteZh/Caduceus

Caduceus Overview

为什么要做这个

Hermes Agent 在使用过程中会自己做以下事情:

  • 写新 skill、patch 旧 skill
  • 把”用户偏好”和”环境约定”塞进 memory
  • 自动排队 task 在你不看的时候执行
  • 装载 plugin 提供新工具

这些都是长期影响 agent 行为的状态。但它们以裸 markdown / JSON / SQLite 的形式分散在 ~/.hermes/ 下,没有统一的视图。我经常想问:

  • agent 创建了哪个 skill —— 在哪次 session 创建的?
  • 一个 skill 被哪些 session 引用过 —— 它真的有用吗?
  • session 的 tool_calls 里到底跑了什么?花了多少 token?
  • agent 在我不看的时候排了什么 task?哪些跑崩了?错在哪里?
  • 装了哪些 plugin?暴露了什么工具?env 配齐了吗?
  • Soul 改了没?Memory 里都记了我什么?

Caduceus 把以上问题全部摊到桌面上。

只读,不会动 agent 的任何文件:所有 state.db / kanban.db 都以 readonly 模式打开,没有任何写入接口;.env 严格只读 key 名,不读 value、不返回 value、永不向前端透出 value。

启动

1
2
3
4
cd hermes-dashboard
npm install
npm start
# → http://localhost:5273

环境变量:

变量 默认 说明
HERMES_HOME ~/.hermes 数据根目录
PORT 5273 dashboard 端口

技术栈极简:

  • 后端:Node.js + Express + better-sqlite3,零编译
  • 前端:单页 HTML + Alpine.js(CDN)+ 纯手写 CSS 变量驱动主题 + ECharts + marked
  • 构建:无。node server.js 即跑

视图是怎么排的

整个 dashboard 一共 8 个视图,通过左侧导航切换,每个视图都能反向跳到关联的其他视图。

下面挑几个核心视图展开讲讲。

Skills 视图:DOMAIN MAP + AGENT EDITS + GRID

DOMAIN MAP + AGENT EDITS strip + SKILLS GRID 三层结构。

  • DOMAIN MAP:treemap 视图,19 个分类矩形按 skill 数量大小排列,点 tile 联动下方 grid filter
  • AGENT EDITS strip:两类信号合并的药丸列表
    • patched 通过 skill_manage(action=edit) 改的,有完整 rationale + 内容历史
    • drifted SKILL.md md5 ≠ shipped md5 但没有 skill_manage 记录 —— 说明 agent 是用 Write/Edit/sed 等”普通”工具直接改的文件
    • patched-and-drifted 两条都中
  • SKILLS GRID:按 mtime 倒序的卡片列表,最近 7 天修改过的 skill 右上角有绿点

每张卡片点开是 skill drawer,里面藏着我最得意的功能 —— LINEAGE 时间轴。

来自 ~/.hermes/kanban.db 的 agent 任务队列。纯审计视角 —— 不创建、不暂停、不重跑,只回答”agent 在我不看的时候做了什么、哪次崩了、错在哪里”。

  • TASKS 表:每行一个 task + 当前 status pill + 最近一次 run 的 outcome
  • crashed / timed_out / failed 用斜体红色 pill —— 眼睛先抓到出错的
  • task drawer:meta 行 + RUN HISTORY 表 + EVENTS 列

来自 ~/.hermes/plugins/<name>/ 的已装插件。manifest、暴露的 tools/hooks、env 状态、调用过它们的 session,全部在一处。

  • TOOLS chips(sage 墨绿)+ HOOKS chips(clay 暖棕)
  • ENV grid:每个 env 一格 —— ✓ configured / ✕ required / ○ optional
  • CALLED IN sessions:扫 messages.tool_calls.function.name,反向匹配本插件提供的 tool name

合并 sessions / skills / soul / skill-create / tasks / plugins 的全部修改事件,按时间倒序。

每行末尾都有一枚 → audit 主按钮,按事件类型路由到对应 drawer,把时间线变成”审计入口”而不是”只能看时间戳的 log”。

skill-create 用绿色脉冲圆点,task-crash 用红色 outline,从一堆 modified 中一眼能挑出来。

Timeline 视图:每行末尾的 → audit 跳转按钮

真正解决问题的那一块:Skill History

Hermes 通过 skill_manage(action=edit) 自动改写 SKILL.md 时不会在文件系统留 git 痕迹,但每次改写的完整新内容、时间戳、session id、改之前 agent 的推理都被几条独立的信号同时记录下来。Caduceus 把这些信号拼起来还原一条 shipped → patch ×1 → patch ×2 → … → current 的修订链,和 git diff 一样可比对,但还附带 agent 当时的”为什么这样改”。

Skill History 抽屉:META 三栏 + CHAIN 时间链

三栏元数据

栏位 含义
SHIPPED V0 基线,hermes-agent 仓库里出厂版的 SKILL.md
CURRENT 当前在盘的 SKILL.md(可能与最后一次 edit 一致也可能继续被改过)
PATCHES .usage.jsonpatch_count 计数

每栏都附 md5 前 12 位(同色即一致)和定位到 Finder 的链接。Skill 已不在盘上时显式标注 — no longer on disk

CHAIN 时间链

纵向节点序列:V0 / #1 / #2 / NOW。每个 patch 节点附:

  • timestamp + sid(点击跳到对应 session)
  • rationale 折叠块skill_manage 调用之前最近的一条 assistant 消息内容(≤1500 字符),即 agent 当时为何要做这次改写
  • diff base / diff target 两枚 ghost 按钮,把当前节点设为对比的起点或终点

信号合流

最关键的设计决定,是认识到两条独立通道互补,缺一不可

点开看完整的信号清单
信号 作用 触发方式
~/.hermes/hermes-agent/skills/<group>/<name>/SKILL.md shipped baseline(V0),出厂版原文
~/.hermes/skills/.bundled_manifest 每个 skill 出厂内容的 md5(旁证,但实测会因 hermes-agent 升级而 stale)
~/.hermes/skills/.usage.json patch_count / last_patched_at / created_at 通过 skill_manage API 才会自增
state.db.messages.tool_callsskill_manage action=edit|create 完整改后内容 + 时间戳 + session_id;同 message 之前最近的 assistant 文本即 agent 当时的推理 通过 skill_manage 调用
md5(live SKILL.md) vs md5(shipped SKILL.md) drift 检测 任何改文件的方式都会触发
~/.hermes/skills/<group>/<name>/SKILL.md current(在盘版本)

为什么需要两条通道:实测发现 hermes-agent skill(autonomous-ai-agents/hermes-agent/SKILL.md)被改了但 .usage.json 里没有它,因为这次改写没走 skill_manage。只看 patch_count 会漏掉这种 case;md5 漂移检测把它捞回来。

DIFF 视图

纯客户端 LCS 行级 diff,无库依赖(3000 行/侧 上限):

  • 新增 sage 绿底
  • 删除 clay 红底
  • 等行 淡灰前景
  • 头部带 +N / −M 统计

DIFF 视图:shipped vs current 行级对比

数据来源一览

dashboard 的所有展示都来自下面这几条线,没有任何外网调用,没有任何遥测。

视图 数据源
Soul ~/.hermes/SOUL.md
Memory ~/.hermes/memories/*
Skills ~/.hermes/skills/<group>/<name>/SKILL.md(递归)
Sessions 列表 state.dbsessions
Session 详情 state.db.messages + ~/.hermes/sessions/session_*.json 兜底
Skill 使用记录 扫描 messages.tool_calls + 所有 session JSON
Skill 修订链 shipped baseline + state.db.messages.tool_calls 中每个 skill_manage(edit) + 紧邻的 assistant 消息 + current
Tasks ~/.hermes/kanban.db(readonly)
Plugins ~/.hermes/plugins/<name>/plugin.yaml + README.md
Plugin env status ~/.hermes/.env 与每个插件 .env.templatekey 名对照

关于主题

右上角 ◐ edition / ◑ console 切换:

  • EDITION(默认):cream #f6f6f3 + 墨黑 + sage #1e9974 + clay #b8714e。白天用,editorial 期刊的范儿
  • CONSOLE:近黑 #0c0c0b + 米白 + 同样的 sage / clay 强调。夜里看终端、做长 session 审计

切换状态写入 localStorage,ECharts 图表会重绘适配新调色板。

写到最后

写这个工具的过程,本身就成了它要解决的问题的镜像:agent 在帮我改 skill 的时候改了什么、为什么要这么改、改完之后下一次 session 又被怎么用 —— 这些是我以前只能通过肉眼比对去回溯的。因此就有了 Caduceus。

如果你也在用任何会自己改自己的 agent,欢迎抄走这个思路 —— 把所有”agent 自改痕迹”的信号通道都列出来,找出它们的互补性,然后把它们合流成一条可比对的修订链。