智能体回答审查器:每一轮 agent 的最终输出由独立 LLM 重新打分(1-100),低于阈值时携带具体反馈引导 agent 自我修正。配置经 127.0.0.1 本地 HTTP 服务实时修改,也支持 dsh-better-sidebar 侧栏配置页。
安装
# npm 包(预构建)
dsh plugin --profile web add dsh-answer-reviewer
# GitHub 源码(首次需按提示配置 allowBuilds 构建授权后重试)
dsh plugin --profile web add github:bycall/dsh-answer-reviewer
装任何插件都等于在你的机器上跑第三方代码,权限和你本人一样大——能读你的文件、用你的凭据、访问网络,工具审批管不到它。GitHub 来源的插件还会在安装时执行构建脚本——pnpm 默认拦截,所以安装可能停在 ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED 或 ERR_PNPM_IGNORED_BUILDS;dsh 会打印出需要添加的确切键名,把它加进该 profile 的 pnpm-workspace.yaml 的 allowBuilds 下,重跑一次即可装上。放行构建本身就是一次信任判断:请只安装可信来源,并尽量锁定 commit(github:owner/repo#sha)。
README
该插件的 README 只有英文版本。
A dsh host plugin: every time the agent is about to close a turn, the
plugin extracts the assistant's final user-facing text and asks a dedicated
review model to grade it on a 1-100 scale. If the score is below the
configured threshold (default 80), the plugin steers the agent back
into the same turn with the reviewer's single most important finding as
concrete feedback. Once a turn has been steered maxChallenges times
(default 5), the agent's reply is allowed to close the turn as-is — the
cap is a leak guard, not the real gate; the score threshold is.
Tune the gate, switch the reviewer on/off, change the max-challenges cap,
or pick a dedicated review model — all live, no host restart — through a
self-hosted config UI on http://127.0.0.1:3987 (or via the same
JSON API from curl). The on-disk config lives at
~/.dsh/answer-reviewer.json (override with REVIEWER_CONFIG_PATH);
turns of the host go through with the defaults if the file is absent.
The review model sees every real user prompt the user has issued in
the session (any user/message event whose source.kind is not
'plugin'), tagged <all_user_prompts>, plus the assistant's final
reply under <reply>. Plugin-injected context (file-change notices,
AGENTS.md, skill content, system reminders) is filtered out so the
reviewer scores against what the user actually asked, not against the
context the harness happened to be carrying.
The review model is independent from the agent's working model when
reviewProvider / reviewModel are configured; otherwise the plugin
falls back to the agent's current route so it works out of the box.
Why a separate model?
The review model uses a strict JSON-only grader prompt ({score, reason})
that the agent never sees directly. The findings are injected back as a
user/message with source: { kind: 'plugin', plugin: 'dsh-answer-reviewer' }, so the agent treats them as user input — but
the agent is explicitly told not to mention the review to the user. A
different provider/model reduces the chance that a self-graded check
rubber-stamps its own work, and the explicit numerical gate means a
well-tuned model that returns 80+ will not trigger any re-attempt.
Live config (since 0.4.0)
The plugin starts a tiny node:http server bound to 127.0.0.1:3987
(no external access). Open it in a browser to see the form, the on-disk
config path, and a rolling list of the last 15 review outcomes. The same
JSON API is reachable from the shell:
# Read current effective config + overrides + file path
curl -s http://127.0.0.1:3987/api/config
# Tweak the threshold (only the fields you POST are written)
curl -s -X POST http://127.0.0.1:3987/api/config \
-H 'content-type: application/json' \
-d '{"threshold": 90}'
# Disable the reviewer
curl -s -X POST http://127.0.0.1:3987/api/config \
-H 'content-type: application/json' \
-d '{"enabled": false}'
# Wipe overrides back to defaults (also removes the on-disk file)
curl -s -X DELETE http://127.0.0.1:3987/api/config
# See the last 20 review outcomes
curl -s http://127.0.0.1:3987/api/recent
Disable the server with REVIEWER_HTTP=0. Change the port with
REVIEWER_HTTP_PORT=<n>. Move the on-disk file with
REVIEWER_CONFIG_PATH=<abs path>. The next turn picks up the new value
with no host restart.
Side card (since 0.5.0)
If the host profile also installs
dsh-better-sidebar,
the plugin also registers a right-sidebar tab titled "Reviewer 配置".
Open it from the sidebar's + menu (next to Files / Terminal / Browser).
The tab content is the same form as the standalone server, loaded
inside an iframe — saving a value through the tab is observable to the
standalone server (and vice versa) on the very next GET. When the tab
is not active the iframe unmounts so background tabs do not keep
polling.
If dsh-better-sidebar is not installed, the side card is hidden and
the standalone 127.0.0.1:3987 page is the only surface. Both are
optional and the plugin works with neither installed.
The better-sidebar dependency is soft, by design: the tab is waited for
lazily (ctx.inject(["betterSidebar"], …)) rather than declared in
exports.inject. A hard inject would leave the client entry
pending (waiting for service: betterSidebar) on any host without
dsh-better-sidebar, which the web boot audit reports as
web boot: 1 entry did not activate and the shell renders as a
"Failed to load plugins" banner over the whole main page. See the 0.5.3
changelog entry.
Install
Add the package to your profile's dependencies and to
dsh.profile.bundles, then refresh the host.
// ~/.dsh/profiles/web/package.json
{
"dependencies": {
"dsh-answer-reviewer": "file:/Users/bycall/Downloads/workbuddy/Claw/dsh-answer-reviewer"
},
"dsh": {
"profile": {
"bundles": [
"dsh-answer-reviewer"
]
}
}
}
Then dsh plugin --profile web install and restart the host.
Configuration
| key | type | default | meaning |
|---|---|---|---|
enabled |
boolean |
true |
Kill switch. Set to false to disable without uninstalling. |
threshold |
number |
80 |
Inclusive 1-100 score gate. Below this, the plugin steers. |
maxChallenges |
number |
5 |
Hard cap on steers per (session, turn). Hard-capped at 8. |
maxReviewTokens |
number |
512 |
Output cap for the review model. |
timeoutMs |
number |
60000 |
Wall-clock cap on the review call. |
reviewProvider |
string |
agent's | Provider for the dedicated review model. Must pair with model. |
reviewModel |
string |
agent's | Model id for the dedicated review model. Must pair with provider. |
threshold must be an integer in [1, 100]; out-of-range values fall
back to the default rather than being silently clamped (so a mis-set
config does not quietly change the gate from "80" to "100").
reviewProvider and reviewModel must be supplied together; supplying
one without the other is a configuration error and the plugin will
refuse to mount.
Behaviour
- Subagent turns are skipped. The plugin only reviews the user-facing
agent.
agent.session.header.origin === 'subagent'is filtered out. - Empty / interrupted assistant output is skipped. No review means no steer.
- Review call failures are fail-open. Network errors, timeouts, parse failures, or model-side errors all log a warning and let the turn close normally. A flaky reviewer must never wedge the host.
- Aborted signals short-circuit. If the user paused or cancelled the turn, the review is cancelled mid-flight and the turn is left alone.
- Out-of-band scores fail closed. The review model must return an
integer in
[1, 100]with a non-emptyreason. Anything else (model returns "1.5", "99.9%", "I score this ...", etc.) is treated as a parse failure — failing open would let a misbehaving reviewer buy a pass by returning garbage. - Per-turn counter is capped. Once a turn has been steered
maxChallengestimes, subsequent turn boundaries inside the same turn are allowed to close without review. With the default5you will see at most five steered re-attempts before the agent's reply goes to the user as-is, even if the review model still scores it below the threshold — the cap is a hard budget, not a soft hint. - The agent is told not to mention the review. The steer message explicitly instructs the agent to address the finding silently.
Test
node test/smoke.mjs
The smoke test exercises every pure helper plus the fail-open,
skip-subagent, below-threshold, and cap-exhausted branches of
onTurnStopping with mock objects (mocked BlockAssembler-shaped stream
chunks). It does not boot a dsh host.
Files
lib/index.js— cordisapply, wires the listener and orchestrates the review call. Imports from./internal.js.lib/internal.js— barrel re-export solib/index.jsand tests import from one place.lib/review.js— pure helpers:resolveConfig,extractAssistantText,extractUserPrompts,buildReviewPrompt,parseScore,isScoreAcceptable,buildSteerMessage,createChallengeCounter, and the publicConfigzod schema.lib/config-store.js—createConfigStore(persistent, hot-reloadable),defaultConfigPath, env-var constants for the HTTP server.lib/server.js—startServer(store, opts)— the 127.0.0.1-onlynode:httpinstance (HTML form + JSON API).lib/client.js—window.__ModuleLoader__.loadclient bundle. When the host profile includesdsh-better-sidebar, registers a "Reviewer 配置" side card; otherwise the entry still activates and the card is simply never registered.cordis.patch.yml— cordis bundle entry that mounts the plugin.test/smoke.mjs— node ESM smoke test (48 cases).CONFIGURE.md— detailed configuration guide (default vs independent review model, threshold tuning, fail-closed/fail-open matrix).CHANGELOG.md— versioned release history.
Further reading
CONFIGURE.md— answers "how do I configure an independent reviewer model", "how should I pick a threshold", and "what does this plugin do on bad config". Read it before tuningthresholdor wiring up a second provider.CHANGELOG.md— the breaking change in0.3.0is the threshold/maxChallenges split; if you are upgrading from0.2.x, read the migration note there.
链接
同类插件
FuRongJun-1999/dsh-memory★ 176
白箱AGI架构探索:元认知(自我认知循环)、持续学习(知识飞轮)、世界模型(条件空间+语义时空图)、自我改进(自举纪律)、零LLM白箱管线与可审计信任护栏。
Jonah-Wu23/dsh-gungnir#dsh-plugin★ 3
面向 DeepSeek Harness 的证据驱动目标校验插件。通过 /ultragoal 锁定目标,并依据命令退出码与生成产物验证完成状态,防止模型虚报任务完成。
kenz1117/dsh-engram★ 2
跨会话长期记忆插件,以「记忆宫殿」隐喻重构 agent 长期记忆:双层 SQLite 分库(用户级 + 按 git origin 隔离的项目级)、FTS5 + 本地向量 RRF 融合与新鲜度/命中排序 boost、从会话日志自动摄取(含末轮)、来源审计链、巩固蒸馏与衰减遗忘,以及设置页「记忆库」面板(走廊拓扑、导览管家、翻新清单)。
CAI-MH/dsh-quality-review★ 1
每轮回复结束时用独立审查模型审核输出,判定不合格则引导 agent 修复,每轮最多追问 2 次;可注入 SOP 文件夹标准作为额外审核维度。
yunxiyang/dsh-loop-continue★ 0
续跑「叙述了下一步动作却没调用工具」就结束的 agent 轮次:插件在 agent/turn-stopping 回放该轮日志,让裁判模型回答一次 true/false,命中的话把同一轮再推进一步。
社区评论
评论公开保存在 GitHub Discussions。加载评论会连接 GitHub 和 Giscus;发表内容需要 GitHub 账号。