Skip to content
dsh.fish
Bundle

dsh-approval-ai-review

AI-reviewed auto-approval for DeepSeek Harness: judges each pending approval ask with one auxiliary model call before any human answerer runs, auto-grants the safe ones, and escalates everything else with the review analysis attached

Source
kiiiiile
License
MIT
Updated
Updated 11 hours ago

Readme

# dsh-approval-ai-review

English | [中文](README.zh.md)

A [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) plugin (`dsh-plugin`).

## Install

```sh
dsh plugin --profile web add dsh-approval-ai-review
```

Or from GitHub: `dsh plugin --profile web add github:kiiiiile/dsh-approval-ai-review`.

## Summary

Use this package to stop confirming every safe tool ask by hand. When enabled for a session, each pending approval ask is judged by one auxiliary model call before any human answerer runs: a safe operation is granted automatically, and every other outcome — escalation, review failure, deadline, malformed output — reaches the remaining answerers with the review's analysis (what the operation would do, what it risks) attached to the request. The plugin ships inert: without `/auto-approve on` (or `enabled: true`) it delegates every ask unchanged.

## Table of Contents

- [Use this package](#use-this-package)
- [Understand the implementation](#understand-the-implementation)
- [Model Experience](#model-experience)
- [Known Limitations and Deferred Work](#known-limitations-and-deferred-work)
- [Dev Note](#dev-note)

-----

<a id="use-this-package"></a>
## Use this package

Compose this plugin after `@deepseek-ai/dsh-user-approval` in deployments whose permission mode still asks: the reviewer consumes the same `approval/request` waterfall the Web and ACP answerers listen on, registered with `prepend` so it settles before any prompt appears. It requires `ctx.llm`; the review call reuses the agent's routed conversation model unless an explicit route is configured.

```yaml
- name: 'dsh-approval-ai-review'
  config:
    enabled: true
```

| Field | Default | Meaning |
|---|---|---|
| `enabled` | `false` | Composition default for sessions without an `approval/review/enabled` override |
| `reviewProvider` / `reviewModel` | follow the conversation model | Explicit review route; must be supplied together |
| `reasoningEffort` | `low` | Reasoning effort for the review call (`off`/`low`/`high`/`max`) |
| `maxTokens` | `2048` | Output-token cap for one review call |
| `reviewTimeoutMs` | `30000` | End-to-end review deadline |

The exhaustive configuration surface is the `Config` schema in [`src/index.ts`](src/index.ts).

### Toggling per session

`/auto-approve [on|off]` appends the durable `approval/review/enabled` switch to the session log (the last event wins; without one the composition default applies), and bare `/auto-approve` reports the current state. The switch is log-only: the model never sees it.

### Deciding one ask

The reviewer receives the tool name, the exact `tool/call` arguments from the session log when the ask carries a call id, and the asker's reason. A validated `{"verdict":"approve"}` resolves the ask `allowed-once`. Anything else — an `escalate` verdict, a transport error, a fired deadline, malformed output — delegates the ask to the remaining answerers with the review's analysis appended to the request reason, so the human prompt states what the operation would do and what it risks. Without a composed answerer the seam's own fail-closed `unavailable` applies.

-----

<a id="understand-the-implementation"></a>
## Understand the implementation

<details>
<summary>Implementation internals — click to expand</summary>

The observable behavior is covered in [Use this package](#use-this-package); this section explains dispatch, the review call, and the log path.

### Source map

| File | Role |
|---|---|
| [`src/index.ts`](src/index.ts) | Plugin registration, the prepend `approval/request` listener, the `/auto-approve` command |
| [`src/reviewer.ts`](src/reviewer.ts) | Route resolution, reviewer prompts, verdict parsing, session-log reads |
| [`src/types.ts`](src/types.ts) | Config, verdict vocabulary, and the three log-only session events |

### Dispatch and the reason mutation

The `prepend` listener owns safe decisions and delegates everything else. On escalation it mutates the shared request's `reason` (the mutate-then-delegate pattern of the waterfall contract) so downstream answerers present the analysis; the committed `approval/asked` audit event already carries the asker's verbatim reason, so the log is unaffected.

### The review call

The call is one hand-built `ctx.llm.stream()` request with the review system prompt and a JSON-framed input; the exact route, effort, prompts, and cap are appended as `approval/review/request` BEFORE dispatch, and the parsed verdict as `approval/review/verdict`, so the auxiliary request is reconstructable from the session log. A merge-extensible finish reason falls through unknown kinds to failure, and every failure path escalates — this plugin never rejects on its own authority.

</details>

-----

<a id="model-experience"></a>
## Model Experience

### Review decision

#### What the model sees

Nothing new. The three review events and the toggle switch are log-only; the model sees the eventual tool outcome exactly as without the plugin, and the standing `approval:policy` sentence stays accurate because asks still happen — they are answered earlier.

#### Token effect

Zero model-transcript tokens. Each enabled ask costs one auxiliary model call that never enters the conversation transcript.

#### KV Cache effect

Unchanged. The review call is a separate request with its own prefix; the conversation's stable request header and cached prefix are untouched.

## Known Limitations and Deferred Work

<a id="known-limitations-and-deferred-work"></a>

- **The reviewer never denies** — a dangerous ask escalates to a human rather than being rejected automatically; a machine-denial verdict is deferred until a deployment owns that authority.
- **Child sessions do not inherit the switch** — `approval/review/enabled` is not seeded into delegated children the way `approval/policy` is; a child review default is deferred until delegation owns one.
- **The review consumes wall-clock time inside the ask** — a slow model extends the pending ask; `reviewTimeoutMs` bounds it but does not shorten the caller's own tool-timeout budget.
- **Effort validation is per-route** — `reasoningEffort` values the routed model does not advertise fail the review (which escalates) rather than the boot; the compatible set is not known until a route exists.

<a id="dev-note"></a>
### Dev Note

<details>
<summary>Working context for maintainers — click to expand</summary>

No `./invariant` companion: the plugin owns no relationship whose observations can diverge — the review request/verdict pair is validated at the model-JSON boundary, and the ask/decided audit pair belongs to `dsh-user-approval`'s invariant.

</details>

Install

dsh plugin --profile web add github:kiiiiile/dsh-approval-ai-review

Profile: web

  • This package builds from source on install. pnpm will ask you to allow its build script — that is permission to run the package’s code on your machine, outside the agent sandbox. Only allow sources you trust.
  • This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.
Source