catsenior507/dsh-tool-failure-journal

dsh-tool-failure-journal

Durable failure journal for dsh tool calls: every abnormal exit appended to JSONL on disk, folded by error signature, readable back by the agent.

待分类agent-observabilitycordiscordis-plugin

失败日志 · Failure Journal

同一个错误犯五次,是一行记录 —— 在它滚出上下文之前就已经落盘。

一个 DeepSeek Harness 宿主插件:把每一次异常退出的工具调用追加到磁盘上的 JSONL 日志,把近似相同的失败折叠成带计数的签名,并通过一个工具 failure_journal 交还给 agent。

English · 简体中文


要解决的问题

编程 agent 不只是会出错,它会以同样的方式反复出错,而证据在任何人能看出模式之前 就已经离开了上下文窗口:工具结果被折叠、被压缩,或者干脆被后面四次尝试埋掉。

宿主确实有一份会话日志 —— 但会话日志就是模型的上下文。它会被重写、折叠、裁剪。 它不可能是失败历史该待的地方。

所以这个插件维护了第二份记录,在上下文窗口之外,用一种比进程活得更久的格式。

它捕获什么

只挂一个 tools/result 监听器 —— 那是宿主对一次调用的最终通知,在 pre-policy、guards、工具本体、post-policy、输出校验全部走完之后才发出。 这一个钩子就覆盖了:

  • 工具抛异常
  • 工具返回值违反了它自己声明的 output schema
  • 工具名不存在
  • 分发前就被拒绝的调用
  • 被调用方取消的调用

一个钩子顶四个,而且不需要轮询。

每条记录自带上下文,因为一条记录必须在它所属的会话消失之后依然有用:

{"v":1,"at":"2026-09-11T00:31:07.412Z","sessionId":"session-c9a2…","callId":"call_00_…",
 "turn":12,"step":3,"tool":"pwsh","aborted":false,"errorCode":"COMMAND_NOT_FOUND",
 "message":"'lake' is not recognized as an error…","messageHead":"…","argsChars":41,
 "args":"{\"command\":\"lake build\"}","content":"…","signature":"9f2c1ab73e04",
 "tag":"failure","recurrence":3,"recurring":true,"firstSeenAt":"2026-09-11T00:28:51.003Z",
 "remediation":["The binary is not on PATH for this shell; …"]}

它为什么不止是一份日志

签名(signature)。 每个失败都会被哈希成一个稳定 id,由工具名、错误码和一条 归一化后的消息组成 —— 路径、时间戳、UUID、长数字都被替换成占位符。 四十次近似相同的失败折叠成一行带计数,这才是人能据以行动的单位。

regression: true。 一个在本次会话中先成功过、之后又失败的工具会被标记。 这是日志里最强的信号:本来能用的东西坏了,它指向期间发生的任何改动。

aborted 不算 failure。 被撤回的调用单独打标,永远不计为缺陷。把取消算作失败 会污染复发统计 —— 而复发统计正是这份日志值得读的原因。

内置修复提示。 对成因明确的那几类 —— EDIT_NO_MATCH、EDIT_NOT_UNIQUE、 COMMAND_NOT_FOUND、TIMEOUT、PERMISSION_DENIED、SYNTAX、BAD_ARGS —— 记录里直接带上成因,以及与刚才那次不同的下一步动作。

工具

action回答什么问题
stats什么在反复坏,按签名折叠。先看这个。
list最近失败了什么,最新在前
show某个签名的全部出现记录(支持前缀)
sessions磁盘上有哪些日志文件、多大
clear归档当前页,重新开始
selftest证明观察器已挂载、目录可写

stats 是真正改变行为的那个。"同一个错误犯了五次"以 count=5, recurring=true 一行呈现,和五段独立的堆栈,对读者来说是两条不同的指令。

安装

本插件和其他所有 dsh 插件一样,作为包安装进一个 dsh profile。 dsh plugin 会在 profile 目录里转发给 pnpm,所以 pnpm 接受的任何 spec 都可以用。

# 从 GitHub 安装(这是公开发布形式)
dsh plugin --profile web add github:catsenior507/dsh-tool-failure-journal

# 本地检出安装(开发时用)
dsh plugin --profile web add /absolute/path/to/dsh-tool-failure-journal

web 是自带 GUI 的 profile;可以换成 headless、sdk、acp 或你自己的 profile 名。 Windows 上路径请用正斜杠,或给路径加引号。

装完重启宿主让 profile 重新组装,然后用这条确认:

failure_journal action=selftest

selftest 会报告观察器是否已挂载,并往日志目录写一个探测文件以证明可写。

安装不会做的事

  • 没有构建步骤。 发布的 JavaScript 就是源码 —— 没有 dist,没有打包器, 也没有 prepare 脚本,所以安装时不会执行任何东西。
  • 没有依赖。 dependencies 和 peerDependencies 都是空的;插件只需要它被加载进的 那个宿主。cordis 由宿主在运行时提供。
  • 没有原生代码、没有编译器、运行时不需要网络。

需要 Node.js 20 或更新版本 —— 因为宿主本身就有这个要求。

写到哪里

<DSH_HOME>/failure-journal/sessions/<sessionId>.jsonl,每个会话一个文件, 按大小轮转(maxBytes 默认 4 MiB,保留 maxRotated 代)。

轮转后的旧文件是保留而不是删除:一个会话把同一个错误循环五十次, 正是事后值得读的情形,而在这个文件刚变得有意思的时刻把它截断就本末倒置了。

配置

dsh plugin add 已经替你插入了插件行。要改默认值,编辑 profile 的 cordis.patch.yml 里那一行的 config:

- insert:
    - id: tool-failure-journal
      name: '@dsh-external/dsh-tool-failure-journal'
      config:
        maxBytes: 4194304
        clusterThreshold: 3
        excludeTools: ['todo_write']
        recordSuccesses: false
        exposeTool: true

recordSuccesses: true 会把成功的调用也记下来 —— 用于回答"这条命令到底干了什么"。 注意:用于 regression 信号的成功追踪无论如何都在跑,这个开关只决定什么写进磁盘。

值得明说的设计约束

  • 观察器会在进程里每一次工具调用上运行,所以它绝不能抛异常、绝不能阻塞。 每个派生字段都通过全函数访问器读取,每次写入都是同步的单行 append —— 微秒级, 而且能在进程于回合中途被杀时保住记录。异步队列会恰好丢掉这个插件存在的理由所在的记录。
  • 存储失败会被计数,而不是被隐藏。 selftest 报告写入、丢弃、轮转的条数, 所以一份停止工作的日志会自己说出来。
  • 这个插件永远不会让一次工具调用失败。 写入发生在宿主会兜住异常的监听器里; 日志无法破坏它所观察的工具。

开发

npm test        # 31 个测试,含真实 cordis 挂载与真实事件

npm test 需要 @deepseek-ai/cordis 可解析 —— 它随 dsh 安装而来。如果你是从检出目录 而不是已安装的包运行,把 node_modules 指向 profile 里的那份;Windows 上目录 junction 即可。

挂载测试在真实的 cordis Context 上注册一个假的 tools 服务,发出真实的 tools/result 事件,再通过工具把日志文件读回来 —— 因为"inject 声明写错了导致 观察器根本没挂上"这一类故障,恰恰是手搓假 context 抓不到的。

文件职责
lib/index.jscordis 插件:解析配置、挂载、注册
lib/observer.jstools/result 与 session/event 监听器
lib/store.jsJSONL 存储、轮转、归档
lib/signature.js失败身份、归一化、修复提示目录
lib/tool.jsfailure_journal 工具

许可

MIT