续跑「叙述了下一步动作却没调用工具」就结束的 agent 轮次:插件在 agent/turn-stopping 回放该轮日志,让裁判模型回答一次 true/false,命中的话把同一轮再推进一步。
安装
# npm 包(预构建)
dsh plugin --profile web add dsh-loop-continue
# GitHub 源码(首次需按提示配置 allowBuilds 构建授权后重试)
dsh plugin --profile web add github:yunxiyang/dsh-loop-continue
装任何插件都等于在你的机器上跑第三方代码,权限和你本人一样大——能读你的文件、用你的凭据、访问网络,工具审批管不到它。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 只有英文版本。
Continue a DeepSeek Harness agent turn when the model only narrated its next action and forgot the tool call.
The agent loop closes a turn as completed the moment a step emits text and no
tool call. A model that writes "now I will re-apply the change" and stops looks
finished to the loop, even though the work is half done.
This plugin listens on agent/turn-stopping, replays the current turn's own
session log, and only asks a judge model one strict true/false question
when the shape matches that failure mode. true steers the same turn to run one
more step; false (or an unparseable answer) lets the turn close.
Install
Install into a profile with the dsh CLI. Its plugin subcommand forwards to
pnpm inside the profile directory, so add/remove behave as usual:
dsh plugin --profile <profile> add dsh-loop-continue
Then restart the profile. The profile's package.json gains the dependency and
dsh.profile.bundles entry, and the bundled cordis.patch.yml inserts the
guard into the layer stack — no manual patch editing is required.
To remove it:
dsh plugin --profile <profile> remove dsh-loop-continue
Working from a local checkout instead? Point the profile at the directory:
dsh plugin --profile <profile> add link:/path/to/dsh-loop-continue
Source edits under lib/ are picked up only on restart.
Config
| field | default | in card | meaning |
|---|---|---|---|
maxContinuations |
10 | yes | hard cap on steering per turn (no infinite loop) |
maxSteps |
6 | yes | newest steps shown to the judge |
maxTailChars |
1500 | yes | trailing-text budget split across the first and last halves |
judgePrompt |
built-in | yes | instruction telling the judge what counts as unfinished |
steerText |
built-in | yes | message injected when the guard steers |
debug |
false | yes | log every evaluation (needs a restart) |
judgeProvider |
null | no | override provider; null/unset = derive from the session route |
judgeModel |
null | no | override model; null/unset = derive from the session route |
judgeMaxTokens |
64 | no | judge output cap |
judgeTemperature |
0 | no | judge sampling temperature |
Deterministic gates
No model call runs unless the turn both:
- ended on a text-only step (no tool call), and
- called at least one tool earlier (so a plain one-shot answer is untouched).
This keeps the extra judge call off ordinary finished turns and only spends it where the model plausibly dropped a pending action.
The judge call is self-contained
What goes out for that one call is deliberately small and built from scratch:
system is judgePrompt, and messages is a single user message holding
renderSummary(...) — the trailing text plus the last maxSteps steps. No
tools are sent, and no session history is replayed. The test
sends the judge a self-contained request pins that shape.
Reusing the session would be cheaper per token, and that is not the
question. The host derives messages incrementally and reuses the frozen
message objects (Session.deriveMessages), so appending one user turn leaves
the whole prefix byte-identical and the provider's prefix cache does hit. That
part works. But the saving is the miss on roughly 1–2k tokens, and the
verdict is this plugin's entire output. Three properties are worth more than
that:
- Determinism.
temperature: 0over a fixed summary answers the same way for the same turn shape. A judge reading the whole conversation drifts with it. - Focus. The question is narrow — does the trailing text promise an action that no tool call performed. Trailing text plus the last few steps is the signal; dozens of earlier turns dilute it.
- Cost that tracks the turn, not the session. The call is O(1–2k tokens) whether the conversation is 3 steps or 300.
If the goal is fewer tokens, shrink the input instead: lower maxTailChars or
maxSteps. That cuts the call without giving up any of the three.
Stopping early
Steering is not free: each continuation costs a judge round-trip plus one more agent step. So the guard also watches whether a steer worked.
When it steers, it records how far the session log had grown. The next time the same turn stops, it checks whether any assistant step in between actually called a tool. If the model only narrated again, the steer was ignored and the turn is closed instead of spending the remaining budget on the same refusal. A model that answers the steer with a real tool call keeps its normal budget.
Configuring the guard
The guard resolves its config on every evaluation rather than freezing it at mount, so a change reaches the very next turn-stopping check. Two ways to set values:
- the user settings file,
~/.dsh/settings.yaml, under aloop-continue:key, or - the profile's own
cordis.patch.yml.
Prefer leaving judgeProvider/judgeModel unset. The guard then judges with
the model the current conversation is already running, which is by definition a
working route; set them only to judge with a different model on purpose.
Editing the prompts
Both prompt texts ship as defaults and are plain config fields, so you can retune the guard without touching code. The package ships a browser half, so the editor is a card under Settings > Plugins > plugin config. It starts collapsed like every other plugin card; click the header to open it:
Loop Continue - resume an unfinished turn
judgePrompt - decision policy [textarea]
steerText - resume message [textarea]
max steering per turn [3] 0..100
steps in the summary [10] 1..100
trailing-text budget (chars) [2000] 100..20000
debug - log every verdict [ ]
The prompts save on blur, not per keystroke: the Host validates and
persists the whole document on every write, and one write per keystroke of a
740-character policy paragraph is wasteful. The numeric fields and the toggle
save immediately. Every change except debug applies to the next
turn-stopping check with no restart; debug is read when the plugin is
loaded, so it needs one.
The same fields are editable in ~/.dsh/settings.yaml, which is also the
surface to use when the plugin runs headless with no browser half loaded:
loop-continue:
judgePrompt: >-
You inspect one coding-agent turn that just ended. Reply with exactly one
word: true if the trailing text promises work that no tool call performed,
false otherwise.
steerText: >-
You described an action but did not call any tool. Emit the tool call now.
Both surfaces write the same namespace, so a value set in the card shows up in the file and vice versa.
What each field controls:
judgePrompt— the judge's whole decision policy. The judge sees a deterministic summary (tool calls per step, trailing text, the human request) and must answer one word. Tighten it if the guard steers turns that were actually finished; loosen it if it misses dropped actions.steerText— what the model is told when a turn is steered. This is the message the model acts on, so phrase it as an instruction to emit the call.
To revert, delete the fields; the built-ins come back.
Hot mount
cordis.patch.yml ships a plain insert — only id + name, no config
and no !!js expressions — so a market hot-mount can add or remove this plugin
as a minimal row. No user-specific endpoint, provider, or key is baked into the
patch: policy is resolved at runtime as described above.
A patch-layer change is replayed in full on every reload
(applyEntryPatches clones the entry list before applying), so a row adds and
removes cleanly and never accumulates.
Whether a patch edit takes effect without a restart depends on the host:
- Under the CLI (
dsh profile),runProfileinstalls an HMR service and registers the profile and user patch files with it, so patch edits are applied live. - Under DSH Desktop that path is not taken — the desktop shell composes the
profile itself (
dsh-app-boothelpers) and never loadscordis-plugin-hmr, so acordis.patch.ymledit needs a profile restart.
Edits to lib/*.js always need a restart: the dsh HMR service is created with
root: [], so no source directory is watched for module replacement.
Development
src/ holds the sources; lib/ holds what npm publishes and what a profile
loads. Nothing derives one from the other at install time, so the copy is
explicit and checked:
npm install
npm run build # src/ -> lib/
npm run check # fails when the two differ
npm test # vitest, against src/
prepublishOnly runs check then test, so a stale lib/ cannot be
published. CI runs the same steps plus a pack assertion.
链接
同类插件
FuRongJun-1999/dsh-memory★ 318
白箱AGI架构探索:元认知(自我认知循环)、持续学习(知识飞轮)、世界模型(条件空间+语义时空图)、自我改进(自举纪律)、零LLM白箱管线与可审计信任护栏。
kenz1117/dsh-engram★ 13
跨会话长期记忆插件,以「记忆宫殿」隐喻重构 agent 长期记忆:双层 SQLite 分库(用户级 + 按 git origin 隔离的项目级)、FTS5 + 本地向量 RRF 融合与新鲜度/命中排序 boost、从会话日志自动摄取(含末轮)、来源审计链、巩固蒸馏与衰减遗忘,以及设置页「记忆库」面板(走廊拓扑、导览管家、翻新清单)。
MichengAI/dsh-pua★ 9
提供 /pua,在 Agent 失败后促使其换方法,并在宣称完成前核验收据;含 15 种公司风格、角色模式、全局与会话设置,以及可选验收循环(直到验收命令通过、达到迭代上限或用户取消)。
Jonah-Wu23/dsh-gungnir#dsh-plugin★ 6
面向 DeepSeek Harness 的证据驱动目标校验插件。通过 /ultragoal 锁定目标,并依据命令退出码与生成产物验证完成状态,防止模型虚报任务完成。
Kanadego/dsh-heartbeat★ 6
心跳循环:默认每 20 分钟自己醒一次——维护一份关于你兴趣与偏好的本地画像,按你设的兴趣范围上网搜新东西并存下来。攒到值得分享的内容,就向你绑定的会话送一份简短的素材包。能不能开口由代码里的规则决定(静默时段、忙时窗口、每日上限、冷却),说不说由那个会话里的 agent 自己判断。仅支持 Windows。
AnonyJcy/dsh-j-space★ 4
J-Space Cognition Suite SV1 原生智能体预设与 Cordis 插件:提供 13 大认知工程模块、持久化控制器与状态外化工作区。
社区评论
评论公开保存在 GitHub Discussions。加载评论会连接 GitHub 和 Giscus;发表内容需要 GitHub 账号。