怎么写Writing guide

Harness 自己会写,你也可以亲手写The harness writes them. So can you

一个适配就是一个 JavaScript 文件。模型写的和你写的是同一种格式,过同一道校验关。把团队的规矩写成适配放进仓库,每个人的 Harness 都会照着执行。 An adaptation is one JavaScript file. What the model writes and what you write share one format and pass the same gate. Put your team's rules into the repository as adaptations, and everyone's harness enforces them.

一个适配长什么样Anatomy of an adaptation

一个文件,调用一次 define()。下面这段是实测里模型自己写出来的:这台机器没有 python,它把这个事实写进提示词,并在命令执行之前把它拦下。One file, one call to define(). The model wrote this one itself in a measured run: the machine has no python, so it states the fact in the prompt and refuses the command before it runs.

define({
  title: 'Route python invocations to python3',
  description: 'This machine has python3 and no python: refuses python and says what to run instead.',
  capabilities: ['deny', 'prompt'],
  setup(h) {
    h.promptSection('On this machine there is no `python` executable; use `python3`.')
    h.beforeTool({ tool: 'bash', command: /(^|[;&|(]\s*)python(\s|$)/ },
      () => h.deny('`python` is not installed on this machine. Use `python3` instead.'))
  },
})
字段Field含义Meaning
title简短的祈使句。界面、/rsi 和给模型的通知里都显示它。A short imperative. The UI, /rsi and the notices to the model show it.
description一句话:它消除什么麻烦,怎么消除。One sentence: which trouble it removes, and how.
capabilities代码用到的每一种能力。声明了没用、用了没声明,都过不了校验。Every capability the code uses. Declared but unused, or used but undeclared, both fail validation.
setup(h)在 h 上注册处理函数。h 是适配接触 Harness 的唯一通道。Registers handlers on h, the only way an adaptation reaches the harness.

不能 import,不能 require,没有 process,没有网络,没有文件访问。No import, no require, no process, no network, no file access.

API 一览The API

注册Registration什么时候运行When it runs可以返回May return
h.beforeTool(filter, call => …)工具调用执行之前Before a tool call runsh.deny(reason) · h.ask(reason)
h.onToolResult(filter, (call, result) => …)工具结果返回之后After a tool result comes backh.annotate(text) · h.block(feedback) · h.replace(text)
h.beforeStep(state => …)每次模型请求之前Before every model requesth.note(text)
h.beforeStop(async turn => …)这一轮即将结束时When a turn is about to endh.continueWith(text)
h.promptSection(text)一直生效Always in effect系统提示词里的一段常驻内容A standing section of the system prompt
h.addTool({ name, description, parameters, run })模型调用这个工具时When the model calls the tool字符串(需批准)A string (needs approval)
await h.exec(command, { timeoutMs })在 beforeStop 和 addTool.run 里Inside beforeStop and addTool.run{ ok, exitCode, text }(需批准) (needs approval)

过滤器和传进来的数据Filters and the data handlers get

// filter: omit it to see every call
{ tool: 'bash' }                          // or ['read', 'edit']
{ tool: 'bash', command: /regex/ }        // or a 'prefix'; tested against the full command line
{ tool: ['edit', 'write'], path: /regex/ }

// call
{ tool, args, command, commandHead, commandFamily, path }

// result: shell commands report failure through exitCode and text, not isError
{ ok, isError, exitCode, text, bytes }

// turn, in beforeStop
{ calls: [{ tool, command, path, ok, exitCode, mutating, verifying, text }], edits: [paths], verifiedAfterEdits }

处理函数在不适用时返回 undefined。它必须快,不能抛错:抛错不会影响那次工具调用,但累计三次,这个适配就被隔离。A handler returns undefined when the case does not apply. It has to be fast and must not throw: a throw never affects the tool call, but after three the adaptation is quarantined.

六个例子Six examples

下面每一段都用插件自己的校验代码检查过,可以直接复制。Each of these was checked with the plugin's own validation code and can be copied as it is.

1. 拒绝:仓库里不许强推1. Refuse: no force-push in this repository

define({
  title: 'Never force-push in this repository',
  description: 'Refuses git push --force and states the rule, so shared history is not rewritten.',
  capabilities: ['deny', 'prompt'],
  setup(h) {
    h.promptSection('In this repository, never force-push. Add a new commit instead.')
    h.beforeTool({ tool: 'bash', command: /(^|[;&|(]\s*)git\s+push\b[^;&|]*\s(--force(-with-lease)?|-f)(\s|$)/ },
      () => h.deny('Force-pushing is not allowed in this repository. Add a new commit and push that.'))
  },
})

2. 询问:改数据库迁移之前先问人2. Ask: check with a person before editing migrations

define({
  title: 'Ask before editing database migrations',
  description: 'Edits under migrations/ need the user to approve them first.',
  capabilities: ['ask'],
  setup(h) {
    h.beforeTool({ tool: ['edit', 'write'], path: /(^|\/)migrations\// },
      call => h.ask(`This changes a database migration (${call.path}). Migrations that have shipped must not be edited.`))
  },
})

3. 要求验证:没跑测试不许收工3. Demand verification: no finishing without running the tests

define({
  title: 'Run the tests before finishing',
  description: 'Keeps a turn open when files were edited and nothing was run to check them.',
  capabilities: ['verify'],
  setup(h) {
    h.beforeStop((turn) => {
      if (turn.edits.length === 0 || turn.verifiedAfterEdits) return undefined
      return h.continueWith(`You edited ${turn.edits.length} file(s) and have not run anything since. Run \`npm test\` and fix what fails before you finish.`)
    })
  },
})

4. 补充说明:一个常见报错该怎么处理4. Annotate: what to do about a familiar error

define({
  title: 'Explain the stale lockfile error',
  description: 'When pnpm reports an outdated lockfile, says which command fixes it.',
  capabilities: ['annotate'],
  setup(h) {
    h.onToolResult({ tool: 'bash', command: /(^|[;&|(]\s*)pnpm\s/ }, (call, result) => {
      if (result.exitCode === 0 || !/ERR_PNPM_OUTDATED_LOCKFILE/.test(result.text)) return undefined
      return h.annotate('The lockfile is behind package.json. Run `pnpm install --no-frozen-lockfile` once, then repeat the command.')
    })
  },
})

5. 机器事实:缺一个命令5. A fact about the machine: a missing command

就是本页开头那一段。注意它的正则只匹配“在命令位置上运行 python”,不会拦 command -v python:模型要靠这类命令去找替代品。The one at the top of this page. Note that its pattern matches python only where a command is run, so command -v python still works: that is how the model looks for an alternative.

6. 新增工具(需批准)6. A new tool (needs approval)

define({
  title: 'Add a tool that reports the service ports',
  description: 'Gives the model one call for the ports this project uses, instead of searching for them.',
  capabilities: ['tool'],
  setup(h) {
    h.addTool({
      name: 'project_ports',
      description: 'Ports used by the services of this project.',
      parameters: {},
      run: async () => 'api 8787, web 5173, postgres 55432',
    })
  },
})

用到 tool、rewrite、exec 的适配以 proposed 状态等着,直到你运行 dsh-rsi approve <id>,或者对项目里的文件运行 dsh-rsi trust。An adaptation that uses tool, rewrite or exec waits as proposed until you run dsh-rsi approve <id>, or dsh-rsi trust for a file in a project.

放进仓库,让全组用上Into the repository, for the whole team

  1. 建立项目 HarnessStart a project harness

    在仓库里运行一次,然后把 .dsh-rsi/ 提交进去。Run this once in the repository, then commit .dsh-rsi/.

    dsh-rsi project init
  2. 把文件放进去Add your file

    一个文件一个适配,文件名随意。Harness 自己学到的项目层适配,转正后也会写到这里。One file per adaptation, named as you like. Project-layer adaptations the harness learns itself are written here too, once promoted.

    .dsh-rsi/
      harness.json
      adaptations/
        no-force-push.js
        ask-before-migrations.js
  3. 检查,然后接受Check, then accept

    dsh-rsi project 列出每个文件能不能通过检查、本机接受了哪些。仓库里的代码不会因为被克隆下来就运行:每台机器都要读过之后自己 trust,文件变了要重新接受。dsh-rsi project lists whether each file passes the checks and which ones this machine has accepted. Code does not run because it was cloned: each machine runs trust after reading the files, and again when they change.

    dsh-rsi project     # what is there, what passes, what this machine accepted
    dsh-rsi trust       # accept the files as they are on disk now
  4. 在会话里确认Confirm it in a session

    开一个新会话,输入 /rsi:能看到挂载了哪些适配、各属于哪一层、是不是来自项目 Harness。Open a new session and type /rsi: it shows which adaptations are mounted, their layer, and whether they come from the project harness.

dsh-rsi 命令随插件一起装在 profile 里,不在 PATH 上。可以起个别名:The dsh-rsi command is installed with the plugin inside the profile, not on PATH. An alias helps:

alias dsh-rsi="$HOME/.dsh/profiles/web/node_modules/.bin/dsh-rsi"
项目只能让 Harness 更保守。harness.json 里的项目级设置可以把模式调成只记录、关掉某些判断点、调高阈值,但碰不到 autonomy 这类会放宽权限的设置。 A project can only make the harness more conservative. Project-level settings in harness.json can switch the mode to record-only, turn detectors off or raise thresholds, but cannot reach settings such as autonomy that would loosen anything.

写的时候的规矩Rules of thumb

  • 匹配要窄。用具体的命令、报错文字或路径。一个什么都拦的适配过不了回归这一关。Match narrowly. Use the specific command, error text or path. An adaptation that catches everything does not pass the regression stage.
  • 能强制就别只提醒。已知是错的操作,直接 deny 或 block,比在提示词里劝一句可靠。Enforce where you can. For an action known to be wrong, deny or block is more reliable than a line of advice in the prompt.
  • 事实同时写进提示词。关于这台机器或这个项目的事实,再加一句 h.promptSection,后面的会话根本不会去犯这个错。State facts in the prompt too. For a fact about the machine or the project, add h.promptSection, so later sessions do not make the mistake at all.
  • 在命令位置上匹配。行首,或者 ; & | ( 之后。别把只是查看环境的命令也拦了。Match at command position. The start of the line, or after ; & | (. Do not catch commands that only inspect the environment.
  • 要求的检查得是真检查。让它跑项目的测试或者跑改过的代码,而不是只做语法解析。Ask for a real check. The project's tests, or running the changed code, not something that only parses the file.
  • 给模型看的话要短。一两句:事实是什么,该怎么做。Keep what the model reads short. One or two sentences: the fact, and the action to take.

不想手写:让 Harness 从历史里写Rather not write by hand: let the harness mine the past

判断点也能跑在磁盘上存着的历史会话上。加上 --adapt,它会把发现写成待批准的适配,你逐个看过再放行。The detectors also run over sessions stored on disk. With --adapt the findings become adaptations that wait for approval, and you let them through one by one after reading them.

dsh-rsi mine path/to/session.jsonl            # what the detectors find in a stored session
dsh-rsi mine path/to/session.jsonl --adapt    # propose adaptations for it
dsh-rsi show <id>                             # source, validation report, counters
dsh-rsi approve <id>

完整的命令、配置项和已知限制在插件参考文档里。Every command, every setting and the known limits are in the plugin reference.