Letter2025/dsh-approval-llm
Model-based permission approval (approve-for-me) for DeepSeek Harness: an approval/request answerer backed by a separate reviewer model
Listed
6
Security
Bundle verified
What it does
Model-based permission approval: an approval-request answerer backed by a separate reviewer model.
Best for
- Low-risk DSH workflows where routine permission requests can be screened by a separate reviewer model.
- Deployments that want deterministic allow, deny, and human-only routing before model review.
- Teams that retain human escalation for ambiguous decisions, reviewer failures, or repeated denials.
Not ideal for
- High-risk unattended workflows, because the AI reviewer is not a security guarantee and may be affected by prompt injection.
- Policies that require wildcard or argument-pattern routing; configured lists match exact tool names only.
- Workflows requiring batched approval review, a dedicated machine-readable audit event, or a client-side approval badge.
README
dsh-approval-llm
| English | 中文 |
Model-based permission approval (approve-for-me) for DeepSeek Harness.
A community plugin that adds a “model approval” permission mode (approve-for-me) to DeepSeek Harness: in that mode, approval/request asks are answered by a separate reviewer model instead of a human — the reviewer decides ALLOW / DENY / ESCALATE, and the request only reaches a human when the reviewer cannot decide or fails. In every other permission mode the plugin stays silent, so human approval is never front-run by the model.
It is the dsh equivalent of Codex’s approvals_reviewer=auto_review (--approve-for-me), and it follows the review design of AGENTSCOPE-PLAN-058 / 062 / 063 (orthogonal reviewer dimension, three-way decision, routing policy, prompt isolation, fail-to-human, circuit breaker).
Warning: an AI reviewer is a policy choice, not a security guarantee. It can be fooled by prompt injection from tool output or the agent’s own reason. Prefer it for low-risk workflows; keep
humanOnlyList, thedenyList, and the circuit breaker tight.
Design
| Concept (this plugin) | Codex | AGENTSCOPE-PLAN-058 |
|---|---|---|
| A dedicated permission mode activates the reviewer |
approvals_reviewer: auto_review + --approve-for-me
|
UI preset 帮我批准 = DEFAULT + ApprovalReviewer=MODEL
|
| Outside that mode the plugin delegates everything | human approval unchanged |
请求批准 = DEFAULT + HUMAN
|
Answer the approval/request waterfall |
approvals_reviewer: auto_review |
ApprovalReviewer = MODEL (orthogonal to the permission mode) |
| Deterministic routing before any model call | “deterministic sandbox/network allowlist runs before the guardian” |
SAFE_ALLOW / DENY / HUMAN_ONLY routing policy |
Reviewer model decides ALLOW / DENY / ESCALATE
|
Guardian subagent (Approved / Denied / TimedOut / Abort) |
ALLOW / DENY / ESCALATE |
| Reviewer holds an isolated security policy | Guardian prompt isolated from the main agent | §4.5 prompt isolation |
| Tool description injected at review time | — | §4.5 dynamic tool-description injection |
| Tool arguments recovered from the session log | trust layering (arguments reviewed, not just the name) | §4.4 argument-level risk |
| Model failure → hand to human, not counted in the breaker | fail-closed guardian | §4.8 fail-to-human vs policy decision |
| Consecutive DENY threshold → hand off to a human | circuit breaker (3 consecutive) | §4.14 circuit breaker |
Explicit provider/model, else the logged conversation route |
— | PLAN-062 per-agent model config + fallback |
| Decision appended to the session as a user-visible message | guardian badge in the UI | DENY event pushed to the frontend (§4.17.5) |
How it works
approval/request (waterfall)
│
├─ enabled? no ────────────────────────────────► next() (unchanged)
│
├─ mode gate: session preset ≠ modePreset ─────► next() (human approval unchanged)
│ (default modePreset = model-approval)
│
├─ RoutingPolicy (deterministic, no model call)
│ ├─ denyList hit ──────────────────────────► 'rejected'
│ ├─ allowlist hit ─────────────────────────► 'allowed-once'
│ ├─ humanOnlyList hit ─────────────────────► next() (human decides)
│ └─ else: REVIEW
│
├─ circuit breaker: consecutive DENY ≥ max ────► next() (human takes over)
│
├─ Reviewer model (isolated security-policy prompt)
│ input: tool name + description (ctx.tools)
│ + reason + tool arguments (from the session log `tool/call`)
│ decision: ALLOW ──────────────────────────► 'allowed-once' (counter resets)
│ DENY ───────────────────────────► 'rejected' (counter +1)
│ ESCALATE ───────────────────────► next()
│ timeout / parse error / provider error ─► next() (fail-to-human)
│
└─ ALLOW / DENY also append a user-visible decision message to the session,
so the main chain records why the call was approved or denied.
-
ALLOW / DENY / ESCALATE map 1:1 onto the dsh outcome vocabulary (
allowed-once/rejected/ delegate). ESCALATE and model failures never fabricate a rejection — they hand the request to the next answerer (the human UI), and a deployment with no human answerer fails closed (unavailable), exactly like Codex’s fail-closed guardian. -
The mode gate makes the modes exclusive. In the
帮我批准preset the reviewer answers; in请求批准(and every other preset) the plugin delegates, so human approval behaves exactly as before. The two presets share sandbox/approval knobs; the recordedpermission/presetselection tells them apart. -
One terminal answerer per deployment. The dsh approval chain is not a priority list of competing judges — compose one answerer. To keep human override, put a human UI answerer behind this plugin (the waterfall
next()reaches it). - Arguments are read from the session log, not from the approval request (the request deliberately carries no arguments to avoid a second rendering that could drift).
Configuration
All fields are validated by the Loader schema; defaults apply when omitted.
| Field | Default | Meaning |
|---|---|---|
enabled |
true |
Master switch; when false every request is delegated unchanged. |
modePreset |
model-approval |
The permission preset that activates the reviewer. When set, the plugin only answers asks from sessions whose effective preset equals this name; every other session delegates to the human channel. Set to '' to review every ask. |
provider / model
|
unset | Explicit reviewer route. Must be set together; when unset the plugin reuses the conversation route from the last request/header in the session log, and fails to human when the log has none. |
timeoutMs |
60000 |
End-to-end reviewer deadline; on expiry the request is handed to a human (TIMEOUT, not counted in the breaker). |
maxOutputTokens |
256 |
Reviewer output cap. |
systemPrompt |
built-in policy | Custom security policy for the reviewer. The built-in policy is a short allow-by-default, deny-on-critical-harm rule set; see src/reviewer.ts. |
allowlist |
[] |
Tool names auto-approved without a model call (SAFE_ALLOW). |
denyList |
[] |
Tool names rejected outright without a model call. Wins over the allowlist. |
humanOnlyList |
[] |
Tool names that must be decided by a human; never auto-reviewed. |
maxConsecutiveDenials |
3 |
Consecutive DENY threshold per session before the reviewer hands off to a human; 0 disables the breaker. ALLOW resets the counter. |
maxArgsChars |
4000 |
Cap on tool-argument JSON rendered to the reviewer. |
includeArgs |
true |
Recover tool arguments from the session log for the review. |
notifyUser |
true |
Append a user-visible decision message (✅ 模型审批通过/❌ 模型审批拒绝 with the risk and reason) to the session after every model ALLOW/DENY, so the main chain records why. |
Example overlay (cordis.patch.yml of your profile):
- id: approval-llm
config:
provider: deepseek-official
model: deepseek-v4-flash
allowlist: [read, read_image, glob, grep]
humanOnlyList: [delete, terminal_send]
denyList: [job_kill]
maxConsecutiveDenials: 3
Install
Copy-paste for an AI agent — hand this one sentence to any AI coding agent to have it install the plugin for you: “Read https://github.com/Letter2025/dsh-approval-llm/blob/main/README.md and follow its
## Installsection to install thedsh-approval-llmbundle into the DeepSeek Harness web profile, restart thedsh webserver, and verify that the permission selector shows themodel-approval(帮我批准) preset with its shield-sparkle icon.”
As an installable bundle (recommended)
This package declares dsh.bundle.patch in its package.json, so installing it activates a configuration layer that inserts the plugin row and adds the model-approval (“帮我批准”) preset to the permission table — no manual preset config needed:
dsh plugin --profile web add dsh-approval-llm # installs the published npm package
Restart dsh web, then pick 帮我批准 in the permission selector (the Access chip in the input bar, which carries a shield-sparkle glyph) to switch that session’s reviewer to the model. The preset table is process-level, so changing presets requires a dsh restart.
In-box bundle rows resolve from the dsh installation itself; the @deepseek-ai/* imports are peerDependencies provided by the host dsh, so pin your dsh version (the project is in developer preview with breaking changes). Installing from a local checkout instead: pnpm run build, then dsh plugin --profile web add ./dsh-approval-llm from the parent directory.
Bundled skill: configure the reviewer
The package ships one bundled skill (configure-approval-llm, source bundled), so installing the plugin also puts a configuration guide in the skill catalog. Ask any agent to “configure the approval reviewer”, or load the skill directly — it walks an AI-proposes / user-confirms flow: probe the current model and provider settings, write the approval-llm overlay into ~/.dsh/profiles/web/cordis.patch.yml, then present the full config for your confirmation before a restart takes effect. The guide covers choosing a reviewer model (same provider preferred, contextWindow ≥ the main model), and tightening allowlist / denyList / humanOnlyList / maxConsecutiveDenials for your deployment.
As a source overlay (dev)
- insert:
- id: approval-llm
name: './src/index.ts' # path to this package's entry, or an absolute path
config:
provider: deepseek-official
model: deepseek-v4-flash
Run dsh with the overlay (dsh web --patch ./cordis.patch.yml), or merge the row into your profile’s cordis.patch.yml. The source overlay inserts only the plugin row, not the preset — either also install the bundle layer above, or add the model-approval preset to the permission row yourself (a patch replaces the whole row config, so restate every preset):
- id: permission
config:
presets:
read-only:
sandbox: read-only
approval: ask
name: 只读
workspace-write:
sandbox: workspace-write
approval: ask
name: 请求批准
model-approval:
sandbox: workspace-write
approval: ask
name: 帮我批准
description: 审批由独立的评审模型决定;拿不准或模型故障时转人工。
danger-full-access:
sandbox: danger-full-access
approval: never
name: 完全放开
Build & test
The plugin lives inside the DeepSeek Harness checkout at custom_plugin/dsh-approval-llm; @deepseek-ai/* resolves against the checkout’s own node_modules (built lib declarations + @types), so keep the harness built (pnpm run build at the repo root). The node_modules junction into the harness is provided by the checkout.
pnpm run typecheck # tsc --noEmit (strict)
pnpm run test # vitest: 39 unit tests, no network
pnpm run build # tsc emit to lib/ (ESM, relative imports rewritten)
Roadmap
-
Client-side badge & toggle: a browser half (
dsh.clientin this package) can render a shield icon on tool cards whose ask the reviewer decided, and a settings row that writes the plugin’senabled/modePresetto a hot-reloaded settings namespace. The host loader already discoversdsh.clientpackages from the same row, so the install path is unchanged. - Batch review (PLAN-063): one reviewer call for several pending tools needs a batch entry point in the approval service.
- Wildcard / argument-pattern routing in the deterministic policy.
Security model
-
Mode gating keeps human approval unchanged: outside the configured preset the plugin delegates every request, so
请求批准behaves exactly as before the plugin existed. -
Prompt isolation: the reviewer prompt is assembled by this plugin from its own config; the main agent never sees the security policy, so it cannot tailor asks to pass review. Tool descriptions come from the live registry (
ctx.tools.schemas()), arguments from the durable log — the reviewer judges the real call, not the agent’s claim. -
Fail-to-human:
TIMEOUT,PARSE_ERROR, and provider errors produce ESCALATE (delegate), never a fabricated denial, and are not counted in the circuit breaker (PLAN-058 §4.8 separation of model failure from policy decision). -
Fail-closed by composition: a deployment with no human answerer resolves
unavailable, which callers treat as denial. -
Circuit breaker:
maxConsecutiveDenialsconsecutive DENY on one session hands the rest of the session’s asks to a human — the reviewer stops being the judge when it keeps saying no. -
Sensitive data: review input (tool arguments, reason) is used only for the reviewer request and structured logs; it is not stored beyond the ordinary session log and console output. Turn
includeArgsoff if arguments are sensitive.
Known limitations
-
No dedicated log-only audit event. Model decisions are visible in the main chain as
user/messagenotices (durable and replayable), and the built-inapproval/asked+approval/decidedpair records the ask/outcome. A dedicated machine-readable audit event (like asession/approval-llm-requestwith the full review context) is still blocked by the harness persistence policy for out-of-repo event types; when the harness ships a registration surface, this plugin should add one. - No client-side badge (yet). The decision notice appears as a transcript message; a Codex-style shield icon anchored to the tool card needs a client plugin (the package ships no browser half yet). See the roadmap below.
-
One request per model call. PLAN-063’s batch review (one call for several pending tools) needs a batch entry point in the approval service; the dsh seam processes one request at a time. Per-request latency is bounded by
timeoutMsand the reviewer model choice. -
AI-reviewer trust is a deployment decision. The reviewer can be prompt-injected through tool output. Keep
humanOnlyList,denyList, and the breaker configured; do not enable this for high-risk, unattended workflows. - Exact-name routing only. Lists match whole tool names; there is no wildcard or argument-pattern matching. Add patterns as a follow-up if needed.
License
MIT
Frequently Asked QuestionsFAQ
Use the verified command dsh plugin --profile default add github:Letter2025/dsh-approval-llm 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.