# dsh-automode โก
**Claude Codeโstyle auto mode for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)** โ let your agent run hands-free, while a deterministic guardrail and a cost-aware reviewer keep the dangerous stuff from ever executing.
> ๐ **็ฎไฝไธญๆ**: [README.zh.md](./README.zh.md) ยท **English**: [README.md](./README.md)
[](https://www.npmjs.com/package/@log.li/dsh-automode)
[](https://www.npmjs.com/package/@log.li/dsh-automode)
[](./LICENSE)
[](https://github.com/log-li/dsh-automode)
[](https://github.com/log-li/dsh-automode)
[](https://github.com/log-li/dsh-automode)
[](https://github.com/topics/dsh-plugin)
---
dsh-automode sits between your agent and the harness. It intercepts every tool call **before execution** โ hard `deny` rules and curated `allowPaths` decide deterministically (zero LLM cost), and whatever is left goes to a two-stage classifier. Safe actions run on their own; risky ones are blocked, reshaped, or routed to you.
## โจ Key features
- ๐ก๏ธ **Deterministic first line** โ regex `deny` bands hard-block exfiltration, secrets, and system paths before any LLM call; prefix-glob `allow` rules approve routine commands for free.
- โก **Zero-confirmation allowlist** โ `config.allowPaths` is full trust: file ops and bash writes inside it skip the classifier entirely, and escalated calls are auto-granted through the approval bridge (v0.10.0) โ no prompt, no round-trip.
- ๐ง **Cost-aware two-stage classifier** โ a one-token filter pre-screens; only flagged actions get the structured review, and identical actions reuse the verdict cache for 5 minutes.
- ๐ **Circuit breaker + human fallback** โ 3 consecutive (or 20 total) DENYs pause auto mode and route decisions to a human; one human decision resumes and resets.
- ๐ **Full audit trail** โ every allow / deny / bridge decision is appended to `~/.dsh/auto-mode/decisions.jsonl`.
- ๐ **Native preset** โ flip it on from the permission picker or `/auto`; it plays nicely alongside read-only / workspace-write / danger-full-access.
> โ ๏ธ **Not a sandbox.** The plugin runs inside the [DSH](https://github.com/deepseek-ai/deepseek-harness) process; a deliberately malicious plugin can do anything your user account can do. It reduces unsafe autonomous tool use โ it is not an OS security boundary.
## ๐ Table of contents
- [Install](#install)
- [Commands](#commands)
- [How it works](#how-it-works)
- [Rules](#rules)
- [Configuration](#configuration)
- [Logging](#logging)
- [Architecture](#architecture)
- [Compatibility & contributions](#compatibility--contributions)
- [License](#license)
## Install
```bash
dsh plugin add @log.li/dsh-automode
```
From a local checkout:
```bash
dsh plugin add ./path/to/dsh-automode
```
Restart `dsh web` after installing. The permission picker (bottom-left of the chat box) will show **Auto mode** alongside read-only / workspace-write / danger-full-access.
## Commands
```text
/auto # switch this session to auto mode
/auto-status # show diagnostics: preset, approval policy, breaker state
```
## How it works
```text
Tool call arrives
โ
โโ [pre-execute gate] (all tools, first defense)
โ โ Read-only tools โ allow (unless deny matched)
โ โก Deny rules (regex) โ hard reject
โ โข Allow rules (prefix glob) โ approve
โ โฃ In-tree file ops โ approve (allowInsideWorkingDirectory)
โ โค Escalation intent โ classifier pre-screen
โ โฅ Everything else โ pass through
โ
โโ [approval waterfall]
โ Soft deny rules (prose) โ reject
โก Soft allow rules (prose) โ approve
โข Read-only allowlist โ approve
โฃ Verdict cache hit โ reuse (no second LLM call)
โค Classifier (two-stage: one-token filter โ structured review)
โฅ Failure โ fail-closed
```

> ๐ฑ๏ธ **Interactive version**: [docs/auto-mode-flow.html](docs/auto-mode-flow.html) โ pan/zoom, relationship tracing, dark mode. Diagram source: [`docs/auto-mode-flow.workflow.json`](docs/auto-mode-flow.workflow.json).
The pre-execute gate intercepts ALL tool calls (including those inside the workspace sandbox that would never trigger the approval waterfall). The approval waterfall only runs for calls that actually need sandbox escalation. The pre-execute gate only applies to **auto-mode** sessions; in other presets (read-only / workspace-write / danger-full-access) it is a no-op so it never contradicts the sandbox the user chose.
## Rules
The rule system has two layers:
### Hard boundary (deterministic, never goes to classifier)
- **`deny`** โ regex patterns that hard-reject. First match wins. Evaluated before everything else. Use for exfiltration, secrets, sensitive targets, dangerous commands.
- **`allow`** โ prefix-glob patterns that approve without any LLM call. Evaluated after deny. Use for routine commands you trust completely.
### Classifier guidance (prose, fed to the LLM)
- **`rules.deny`** โ soft-deny descriptions. The classifier reads these as standing rejections. Can be overridden by direct user intent or a matching allow rule.
- **`rules.allow`** โ soft-allow exceptions. The classifier reads these as standing approvals that override matching soft-deny rules.
- **`rules.environment`** โ context facts (trusted repos, infrastructure, cloud buckets). The classifier uses these to judge whether an action is within the user's environment.
All `rules.*` arrays support **`$defaults`**: using `["$defaults", "my custom rule"]` keeps the built-in rules while adding yours. Omitting `$defaults` replaces the entire built-in list for that section.
## Configuration
Configuration goes in your profile's `cordis.patch.yml`. Everything has defaults; a bare `{}` config is valid. The table below is the full reference; a minimal example (the `allowPaths` override) is under [Trusting extra directories](#trusting-extra-directories-allowpaths).
### Key options
| Option | Default | Description |
|---|---|---|
| `deny` | built-in list | Regex patterns that hard-reject. First match wins. |
| `allow` | built-in list | Prefix-glob patterns that approve without LLM. |
| `readOnlyTools` | read, glob, grep, list, search | Tools that default-allow (unless deny matched). |
| `allowPaths` | `[]` | Curated full-trust external directories: file ops and bash write-commands inside skip the classifier, and (since v0.10.0) escalated calls are auto-granted (`approval-bridge`). See [Trusting extra directories](#trusting-extra-directories-allowpaths). |
| `allowInsideWorkingDirectory` | `true` | Allow in-tree file ops without classifier. |
| `classifier.provider` / `classifier.model` | `''` (follow session) | Override the classifier's LLM route. Resolution: `classifier.{provider,model}` โ session's active model โ agent's configured model. |
| `classifier.reasoningLevel` | `off` | Classifier reasoning effort (`off` disables reasoning). If a route rejects the effort, the call retries without it. |
| `rules.deny` | `['$defaults']` | Soft-deny prose for the classifier. |
| `rules.allow` | `['$defaults']` | Soft-allow prose for the classifier. |
| `rules.environment` | `['$defaults']` | Environment facts for the classifier. |
| `failClosed` | `true` | Reject on classifier failure vs. fall back to approval chain. |
| `preExecuteGate` | `true` | Enable the pre-execute gate (only applies to auto-mode sessions). |
| `timeoutMs` | `45000` | Per-call hard timeout for the classifier LLM calls. |
| `classifyContextChars` | `6000` | Char budget for the task-alignment context given to the classifier. |
| `maxArgsChars` | `4000` | Char budget of the command signature used for the verdict cache key. |
| `breakerConsecutive` | `3` | Consecutive classifier DENY to trip the breaker. |
| `breakerTotal` | `20` | Total classifier DENY to trip the breaker. |
### Trusting extra directories (`allowPaths`)
`allowPaths` is a curated **full-trust** list: file ops and bash write-commands whose target resolves inside one of these directories skip the safety classifier entirely (logged as `pre-execute-allow` / `curated allowPath`). The shipped default keeps only the universal `/tmp/` โ **personal directories are configured per profile** in your profile's `cordis.patch.yml`. Loader patches replace the targeted row's whole `config`, so the minimal override below sets only `allowPaths` (every other field falls back to the plugin's code defaults):
```yaml
# ~/.dsh/profiles/