PerryLink/dsh-auto-review
Second-model AI auto-review for DeepSeek Harness approval requests: a read-only reviewer subagent returns structured allow/deny verdicts with reasons, fail-closed by default, fully auditable from the session log (approval/asked -> autoReview/verdict -> approval/decided).
Listed
30
Security
Bundle verified
What it does
Second-model auto-review on the approval answerer chain: a read-only reviewer subagent returns structured allow/deny verdicts with reasons, fail-closed by default.
Best for
- Teams that want a second model to review selected approval requests using workspace evidence and structured reasons.
- Automation workflows that need fail-closed behavior when the reviewer crashes, times out, or returns an invalid result.
- Audited environments that need approval requests, reviewer verdicts, rejection reasons, and final decisions reconstructable from session logs.
Not ideal for
- Workflows that require every approval to remain a direct human decision; AI review can claim requests configured with the `ai` policy.
- Cases where a read-only reviewer cannot gather enough evidence to assess the requested action.
- Environments outside the documented DSH 0.1.0-rc.7 and Node.js ^22.19 or >=24 compatibility range.
README
๐ค dsh-auto-review
Second-model AI approval for DeepSeek Harness โ a read-only reviewer subagent decides allow/deny on the approval chain, fail-closed by default.
When an action crosses the sandbox boundary, a second model reads the evidence and returns a verdict with a reason โ so humans approve nothing while nothing unsafe slips through.
English ยท ็ฎไฝไธญๆ ยท Espaรฑol ยท Portuguรชs ยท เคนเคฟเคจเฅเคฆเฅ
Compatibility
| Surface | Status |
|---|---|
| Harness | DeepSeek Harness 0.1.0-rc.7 (peers pinned to 0.1.0-rc.7) |
| Node | ^22.19.0 \|\| >=24.0.0 |
| Platforms | All (host answerer; optional Web review panel via the session-projection capability) |
| Model | Any (the reviewer inherits the session agentโs route; reviewerModel overrides) |
What you get
dsh-auto-review puts a second model on the approval/request answerer chain:
-
Official seam โ an answerer that claims only the requests it owns (
aipolicy) and delegates everything else vianext(); the human approval flow is never short-circuited. -
Read-only reviewer subagent โ a one-shot fork with a
read/glob/greptool allow-list returns a structured verdict{ decision, reason, riskLevel }. Reviewer asks are recognized by identity and delegated;maxDepth+ the allow-list keep the reviewer non-delegating. -
Fail closed โ reviewer crash, timeout, or schema mismatch resolves through
fallbackPolicy(defaultrejected); a deny verdict feeds its reason back to the calling model. -
Config-driven routing โ per-tool policies (
ai/human/never) plus regex risk rules, all changeable from cordis.yml. -
Deny reasons reach the model โ the reviewerโs reason is injected into the denied tool result (callId-linked); fallback and
never-policy rejections inject auditable markers too ([auto-review]/[auto-review-fallback]/[auto-review-never]). -
Full audit trail โ log-only
autoReview/verdict+autoReview/rejectionsession events (envelopeignorable: true) plus an optional invariant companion enforcing marker โบ event. -
Safety knobs โ a rejection circuit breaker (3 consecutive denials, or 6 of the last 10 verdicts, per turn), a risk-level policy, a one-shot
/auto-review approveoverride, and anever-policy hard disable that explains itself to the model. -
Optional reviewer context โ a bounded compact transcript (
contextBudget) plus a Codex-style Markdown ruling policy (reviewerPolicyText).
Every decision reconstructs from the session log: approval/asked โ autoReview/verdict (or autoReview/rejection) โ approval/decided.
Why a second model instead of rules?
Pattern-based auto-approvers decide before dispatch, with no evidence. dsh-auto-review gives the decision to a reviewer subagent that reads the actual workspace (through its read-only tool face), the already-streamed tool-call arguments (sensitive values redacted), the request reason, and your risk rules โ then returns a structured verdict. A deny verdict feeds its reason back to the calling model, so the agent learns why instead of retrying blindly.
Quick start
# 1. install the bundle into your profile
dsh plugin --profile web add "github:PerryLink/dsh-auto-review#main"
# or from npm (published releases)
dsh plugin --profile web add dsh-auto-review
# 2. restart and verify the row
dsh --profile web --dump-config | grep -A4 'id: auto-review'
Out of the box the shipped patch AI-reviews bash and write; every other tool (including edit โ in-place modification) delegates to the human chain. Add edit: ai explicitly if you accept in-place edits without a human in the loop.
Install & uninstall
-
git channel (latest
main):dsh plugin --profile web add "github:PerryLink/dsh-auto-review#main"โ the isolatedpreparebuild needs the singleallowBuilds: { esbuild: true }key thedshCLI prints fordsh-auto-review. -
npm channel (published releases):
dsh plugin --profile web add dsh-auto-review. -
tarball channel:
pnpm packin this repo, thendsh plugin --profile web add ./dsh-auto-review-<version>.tgz. -
uninstall:
dsh plugin --profile web remove dsh-auto-review(or remove the row from the profile patch).
Configuration
All tunables are Schemastery Config fields (changeable from cordis.yml). An id-targeted override replaces the whole row โ restate every key you need.
| Key | Default | Meaning |
|---|---|---|
enableByDefault |
true |
Sessions start with auto-review enabled; /auto-review on\|off writes a durable override that beats this |
toolsPolicy.default |
human |
Policy for unlisted tools (delegate to the human answerer) |
toolsPolicy.overrides |
{} |
Per-tool policy: ai / human / never
|
riskRules |
[] |
{pattern, policy, field?} matched before the tool table; field selects reason (default), toolName, or arguments
|
reviewerProvider |
fork |
Subagent provider for the reviewer (in-process fork backend) |
reviewerModel |
(inherit) | Reviewer model id; unset inherits the session agentโs route |
reviewerTimeoutMs |
60000 |
Verdict deadline; on expiry the fallback policy applies |
reviewerTools |
[read, glob, grep] |
The reviewer childโs tool allow-list (must be non-empty) |
fallbackPolicy |
rejected |
Reviewer failure: rejected (fail closed) / delegate / allow-once
|
maxReviewsPerTurn |
10 |
Real AI-verdict budget per open turn; beyond it, requests delegate |
maxFailuresPerTurn |
10 |
Reviewer-failure budget per open turn |
reasonMaxChars |
2000 |
Cap for reviewer reasons and the redacted argument preview |
reviewerGuidance |
(none) | Optional advisory guidance appended to the reviewer prompt |
reviewerPolicyText |
(none) | Markdown ruling policy injected into the reviewer prompt (Codex-style) |
denyGuidance |
(anti-circumvention text) | Guidance appended to every injected deny reason |
contextBudget |
{turns: 0, maxChars: 4000} |
Compact transcript budget for the reviewer prompt; turns: 0 disables |
riskPolicy |
{maxAutoAllow: high, onHighRisk: delegate} |
allow verdicts above maxAutoAllow delegate or deny |
circuitBreaker |
{consecutiveDenies: 3, windowDenies: 6, windowSize: 10, action: delegate} |
Rejection circuit breaker |
overrideTtlMs |
300000 |
How long a /auto-review approve override stays usable |
language |
en |
UI language of the /auto-review command output (en | zh) |
allowUnmarkedAudit |
false |
Force session-log audit on hosts that drop the ignorable marker (dangerous: unmarked events make sessions unresumable elsewhere); default is detect-and-degrade |
Example (annotated full form: fixtures/config/config-full.yaml):
- insert:
- id: auto-review
name: dsh-auto-review
config:
toolsPolicy:
overrides: { bash: ai, write: ai }
riskRules:
- pattern: '(?i)(rm\s+(-[a-z]+\s+)*/|git\s+push\s+--force)'
policy: never
- pattern: 'write'
policy: never
field: toolName
reviewerTimeoutMs: 30000
fallbackPolicy: delegate
riskPolicy: { maxAutoAllow: medium, onHighRisk: delegate }
circuitBreaker: { consecutiveDenies: 3, windowDenies: 6, windowSize: 10, action: delegate }
Tools & surfaces
| Surface | Kind | Notes |
|---|---|---|
auto-review |
answerer |
approval/request waterfall answerer โ claims ai-policy requests, delegates the rest via next()
|
/auto-review |
command |
on\|off\|status\|approve [n] โ durable per-session override, budgets, and cumulative statistics |
| deny-reason injection | listener |
tools/post-execute โ verdict / fallback / never reasons fed back to the denied tool result |
autoReview |
session projection | Folded from the log-only autoReview/* events |
| Web review panel | client | Session-header action: switch, budgets, statistics, recent verdicts, one-shot approve |
dsh-eval |
CLI | YAML-driven agent evaluation engine (bin/dsh-eval.mjs) |
| invariant companion | invariant |
dsh-auto-review/invariant (optional; needs the invariants service) |
Session command
/auto-review on|off|status|approve [n]
on/off append the durable autoReview/state override (the fold survives restart/resume โ replay IS the state) and inject a switch notice the model sees (logged as a user/message event). status reports the effective state, both per-turn budgets (AI verdicts and reviewer failures), a tripped circuit breaker when one is active, and the sessionโs cumulative statistics (allows/denies/fallbacks/never rejects, mean duration, recent verdicts). approve [n] records a single-use autoReview/override for the n-th most recent denial (1 = most recent): the next same-tool review within overrideTtlMs carries the authorization as reviewer context โ the reviewer still decides, and the override is consumed by that review regardless of its outcome.
Web review panel
In the Web GUI (web profile), the package contributes a session-header action (AI Review) that opens a panel with the sessionโs auto-review state: the switch with on/off buttons (they execute /auto-review on|off), both per-turn budgets, cumulative statistics (including hard-disable rejections), the circuit trip, the recent verdicts, and one-shot approve buttons for recent denials (they execute /auto-review approve [n]).
How it is wired:
- The host registers an
autoReviewsession projection (folded from the log-onlyautoReview/*events) and serves it through the session-projection channel. - The browser half is a client module (auto-discovered from the
dsh.clientdeclaration) registered on theconversation.session.header.actionsseat. - No extra patch rows are needed: the panel loads whenever the plugin is installed in a profile whose web build provides the session-projection capability (the web profile does). Without that capability the panel reports itself unavailable; the answerer is unaffected.
The panel reads only whole projection values โ it never receives the raw session event stream.
How it works
approval/request waterfall (answerer chain)
โ
โโโโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโ
โ dsh-auto-review answerer โ
โ ยท session enabled? ยท policy = ai? โ no โโ next() โโโถ human answerer (UI)
โ ยท risk rules โ toolsPolicy โ default โ
โโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโ
โ yes
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ reviewer subagent (fork, one-shot)โ
โ ยท toolFilter: read/glob/grep โ
โ ยท outputSchema: {decision, โ
โ reason, riskLevel} โ
โ ยท timeout + req.signal abort โ
โโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโ
โ verdict / failure (fail-closed fallback)
โผ
allow โ allowed-once deny โ rejected + reason injected into the
denied tool result (callId-linked)
โ never โ rejected + [auto-review-never] feedback
โ (hard disable, no reviewer runs)
โผ
audit: approval/asked โ autoReview/verdict | autoReview/rejection
โ approval/decided (session events, log-only, invariant-checked)
Composition order. The answerer runs at its registration position in the waterfall: if a human UI answerer is composed BEFORE the auto-review row, humans answer first and the reviewer only sees what is delegated downstream. Verify with dsh --profile <name> --dump-config and place the auto-review row before your human answerer rows when you want ai-policy tools routed to the reviewer first.
dsh-eval โ agent evaluation engine
Beyond the approval reviewer, dsh-auto-review ships dsh-eval: a YAML-driven agent evaluation platform that runs real headless DSH sessions (one isolated agent + scratch workspace per case, the official Minimal persona as the baseline system prompt), collects the tool-call trace from the session event log, and evaluates structured assertions plus an optional second-model review โ the same reviewer seam as the approval answerer.
# eval/cases/demo.yaml (abridged)
suite:
name: my-suite
cases:
- id: math-output
input: Solve 17 ร 24 and reply with only the final number, nothing else.
expect:
output: { contains: "408" }
- id: glob-trace
seedFrom: '.'
input: Use the glob tool with pattern "src/**" to list the source filesโฆ
expect:
toolCalls: [{ tool: glob, arguments: { contains: { pattern: "src" } } }]
results: [{ tool: glob, contains: "index.ts" }]
Run it (a DeepSeek API key must be in the environment):
dsh-eval eval/cases --model deepseek-v4-flash --timeout-ms 240000 --out .eval-reports
CI gate: the process exits 0 only when every case of every suite passed โ drop it into a GitHub Action step and failing evaluations fail the build. Each case leaves a replayable session JSONL and a trace JSON beside report.md/report.json; assertion results, token usage, and the review verdict are all written into the report files.
Permissions & data
-
Permissions: the workshop manifest declares
session:append,approval:answer,subagent:spawn,command:register, andtools:observe. - Data: nothing is stored on disk; the report ring buffer is in-memory and bounded. No network requests of its own.
-
Session log:
autoReview/*events carry reviewer identity, verdict, reason, risk, and duration โ appended with the envelopeโsignorable: truemarker so any build loads the log. Hosts whoseSession.appendpredates the marker (every released rc line through0.1.0-rc.7โ no release stamps it yet) are detected before the first append (peer-version pre-check, then a probe of the returned envelope) and audit degrades to an in-memory mirror with marker-free feedback, so sessions stay loadable everywhere.
Security boundaries
-
The reviewer is a model. Its verdicts are advisory policy, not a security kernel; prefer
human/neverrules for irreversible operations. -
Fail closed. Every abnormal path (provider missing, capability gaps, start rejection, timeout, non-
completedstop reason, missing/malformed verdict, audit-correlation failure) resolves throughfallbackPolicy, defaultrejectedโ and the rejection feeds an auditable reason back to the model.allow-oncegrants unconditionally; it exists only for unattended deployments whose admin accepts that risk. -
Read-only reviewer. The reviewerโs
toolFilterallow-list (read/glob/grep) cannot write, edit, run bash, fetch the network, or delegate (maxDepth= its own depth). Its session log is persisted and auditable. -
Sensitive arguments are redacted (key-name matching:
token,password,api_key,Authorization, credentials, private keys โฆ) before entering the reviewer prompt; the plugin never executes the reviewed arguments. Redaction is key-based, not content-based โ do not AI-review tools whose argument values you cannot afford to show a model. -
Hard disables explain themselves. A
nevertool or risk rule rejects deterministically AND records a log-onlyautoReview/rejectionevent, then injects a[auto-review-never]marker into the denied tool result โ the model learns the action is hard-disabled instead of retrying it (invariant-checked: marker โบ event). -
Rejection circuit breaker. A run of denials in one turn trips the breaker (
consecutiveDenies/windowDeniesinsidewindowSize), recorded as a log-onlyautoReview/circuitevent; later requests follow itsaction(delegate/reject/abort-turn). -
Reviewer context is presented transcript.
contextBudgetfeeds already-presented session content to the reviewer. With the default same-route reviewer model that content stays inside one provider; configurereviewerModelto a different provider only if you accept presenting that transcript to it. -
neveris one-way at this layer. Anevertool or risk rule rejects before the human chain sees the request โ a lockdown knob, not a default.
Known limitations
- The reviewer needs a working LLM route (inherited by default); without one every review falls back per
fallbackPolicyโ never a silent grant. -
reviewerToolsnames must exist as global tools in the profile; an unknown name fails the reviewer child loudly at the earliest point and falls back. - Risk rules match the request
reason, thetoolName, or the redacted callargumentsper theirfield; other conditions belong intoolsPolicy.overrides. - The
/auto-review approveoverride authorizes the next same-tool review, not the exact historical call; a different action on the same tool consumes it. - The verdict events are log-only; the Web review panel reads the folded
autoReviewprojection (the raw event stream never reaches browser plugins). -
autoReview/stateandautoReview/verdictare appended with the envelopeโsignorable: truemarker on hosts that honor it, so any harness build loads the log โ readers that do not know the out-of-repo types simply skip those records. On released rc hosts (rc.1โrc.7) the runtime detects the dropped marker and never writes these events (the in-memory mirror keeps the command, budgets, breaker, andapproveworking for the session); sessions already polluted by pre-0.5.1 versions can be repaired withscripts/repair-session-logs.mjsfromdsh-permission-rules(its default target set covers all fiveautoReview/*event types). - The git channel needs the single
allowBuildskey thedshCLI prints fordsh-auto-reviewitself. The repo ships its ownpnpm-workspace.yamlwithallowBuilds: { esbuild: true };typescript+tsdownare regulardependencies. - The optional invariant companion needs the
invariantsservice (agent-spine compositions such as headless/ACP); the plain web profile does not provide it, so the row ships commented out in the bundle patch.
Related work
-
Andy8647/dsh-auto-approval โ two-state allow/deny classifier on the
tools/pre-executewaterfall with file-log audit.dsh-auto-reviewdeliberately differs: official answerer chain, always delegates what it does not own, read-only second model with a structured verdict, deny reasons fed back to the model, session-log audit. -
ACP automation bridge โ one-shot machine decisions for its own ACP-owned agents.
dsh-auto-reviewis session- and tool-policy-scoped for the interactive harness; it never infers durable grants.
Development
pnpm install # node ^22.19 || >=24
pnpm run typecheck # tsc: src + tests against the local harness checkout
pnpm test # vitest: 202 tests, 16 files
pnpm run build # tsc declarations + tsdown bundles (lib/, incl. the client bundle)
pnpm run verify:self-contained
pnpm pack # the published tarball
Repository layout: src/index.ts (plugin contract) ยท src/config.ts (Schemastery schema + resolution) ยท src/runtime.ts (answerer, command, deny-reason injection) ยท src/review.ts (reviewer orchestration, prompt, sanitization) ยท src/events.ts (session-event vocabulary + folds) ยท src/audit.ts (host ignorable-marker capability detection) ยท src/projection.ts + src/projection-types.ts (the autoReview session projection) ยท src/invariant.ts (invariant companion) ยท src/eval/ (the dsh-eval engine) ยท eval/ (shipped evaluation composition) ยท bin/dsh-eval.mjs (CLI launcher) ยท src/client/ (browser half) ยท test/ ยท fixtures/.
Topics
deepseek-harness, dsh, dsh-plugin, cordis, approval, auto-review, second-model, ai-safety, sandbox, subagent
Contributors
- @PerryLink โ creator and maintainer: the approval answerer, the reviewer subagent, risk policy and circuit breaker, the session-projection review panel, the invariant companion, dsh-eval, and the five-language docs.
PerryLink DSH Plugin Family
This project is one of the DeepSeek Harness plugins maintained by PerryLink. If this one helps you, the others likely will too:
| Plugin | One-liner |
|---|---|
| dsh-mcp-panel | Read-only MCP runtime panel: /mcp command + Settings tab with status, tools and errors |
| dsh-doublecheck | Engineering-discipline guard: requirements grill, test gates, adversary review |
| dsh-background-agents | Durable background child agents with a Web UI sidebar, messaging and interrupt |
| dsh-lsp-actions | LSP diagnostics, formatting, completion, code actions and rename over language servers |
| dsh-output-styles | Claude Code outputStyles-equivalent runtime style switching |
| dsh-checkpoint-rewind | Claude Code /rewind-equivalent: snapshots, session forks, one-shot restore |
| dsh-permission-rules | Claude Code-style declarative allow/deny/ask permission rules with audit |
| dsh-auto-review | Second-model auto-review on the approval chain, fail-closed by default |
| dsh-memento | Approval-gated cross-session memory: ctx.memory seam + SQLite + memory tool |
| dsh-skill-pack-security | Security-audit skill pack: secret scan, dependency and supply-chain review |
| dsh-session-pin | Pin sessions in the Web sidebar with durable ordering |
| dsh-composer-history | Terminal-style input history for the web composer: arrows, Ctrl+R search |
| dsh-github | GitHub PR/issues integration for DSH, every write gated by approval |
| dsh-plugin-guide | Plugin-development knowledge base as an on-demand agent skill |
| dsh-claude-move | Migrate Claude Code sessions, memory, skills and CLAUDE.md into DSH |
License
Apache License 2.0 ยฉ 2026 dsh-auto-review contributors
Frequently Asked QuestionsFAQ
Use the verified command dsh plugin --profile default add github:PerryLink/dsh-auto-review in a DSH-enabled shell. The command resolves the public package metadata and keeps the plugin attached to the catalog identity shown on this page.
Compatibility follows the bundle and profile status shown above. If a profile is not detected, keep the plugin disabled there and check the repository documentation before enabling it in production.
The GitHub link and activity metadata are the source of truth for releases and maintenance. Revisit this page after a new release to confirm the catalog has observed the latest version.