Bundle
dsh-minimal-anchor
DeepSeek Harness (dsh) plugin: prune the tool schema and inject a structural preamble on a session's first turn only.
- Source
- rand0wn
- stars
- 2 stars
- License
- MIT
- Updated
- Updated 11 days ago
Readme
# dsh-minimal-anchor
[](https://github.com/rand0wn/dsh-minimal-anchor/actions/workflows/ci.yml)
A [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (`dsh`) plugin that shields turn 1 from tool-schema overload.
## Why
A fresh `dsh` session hands the model the *entire* configured toolset — file
edits, bash, subagents, jobs — on message one, even when the first message is
just "look at this repo and tell me what's going on." A smaller, focused
schema on that first turn keeps the model's early reasoning on exploration
instead of premature action, without touching how any later turn behaves.
`dsh-minimal-anchor` hooks the harness's own prompt-assembly pipeline to:
1. **Prune tools on turn 1 only** — down to a configurable whitelist (default: `read`, `glob`, `grep`).
2. **Prepend a short structural preamble** on that same turn, framing the session as exploration-first.
3. **Get out of the way from turn 2 onward** — every later assembly for that session passes through completely untouched, full toolset restored.
## Install
```bash
dsh plugin --profile <name> add dsh-minimal-anchor
```
or from a local checkout:
```bash
dsh plugin --profile <name> add /path/to/dsh-minimal-anchor
```
This adds the package as a dependency of the profile, but does **not** by
itself activate it — `dsh` only applies a package's `dsh.bundle` patch for
packages listed in that profile's `dsh.profile.bundles`. Add the package
name to that list in `profiles/<name>/package.json`:
```json
{
"dsh": {
"profile": {
"bundles": [
"@deepseek-ai/dsh-base",
"@deepseek-ai/dsh-headless",
"dsh-minimal-anchor"
]
}
}
}
```
Confirm it composed with `dsh --profile <name> --dump-config` — you should
see a `minimal-anchor` entry. (An equivalent alternative that skips the
bundles list entirely: insert it directly in your own
`profiles/<name>/cordis.patch.yml` — see [Configuration](#configuration).)
## Usage
Nothing to invoke — it's a passive plugin. Boot your profile as usual
(`dsh web`, `dsh --profile headless "..."`, etc.) and the first turn of every
new session goes out with the pruned tool list and preamble automatically.
## Configuration
```yaml
# profiles/<name>/cordis.patch.yml
- insert:
- id: minimal-anchor
name: 'dsh-minimal-anchor'
config:
whitelistedTools: [read, glob, grep]
enforcePreamble: true
customPreamble: 'Your own turn-1 framing text.'
```
| Field | Default | Description |
| --- | --- | --- |
| `whitelistedTools` | `[read, glob, grep]` | Tool names kept on turn 1. Must match the exact registered tool names in your profile — check `dsh --profile <name> --dump-config` if unsure, names differ from plugin to plugin. |
| `enforcePreamble` | `true` | Whether to prepend the structural preamble section on turn 1. |
| `customPreamble` | (built-in exploration-framing text) | Preamble text, used only when `enforcePreamble` is `true`. |
Extending rather than replacing the default whitelist? `DEFAULT_WHITELISTED_TOOLS`
and `DEFAULT_PREAMBLE` are exported from the package if you're composing config
in TypeScript rather than YAML.
## How it works
Hooks the `system-prompt/assemble` waterfall from
[`@deepseek-ai/dsh-system-prompt`](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/system-prompt),
which runs once per turn and produces the `PromptAssembly` (sections, tools,
contexts) actually sent to the model. A `WeakSet` keyed on the assembly's
scope tracks whether that scope has assembled before; the first time, it
filters `assembly.tools` to the whitelist and unshifts the preamble section,
then calls `next()` so every other listener in the waterfall still runs
normally. Every later assembly for that scope short-circuits straight to
`next()` — no mutation, no persisted per-session state beyond the WeakSet
entry, which needs no explicit teardown since it dies with the scope object.
There is no `agent/request` or per-message hook in the real harness — this
plugin does not use one, unlike an earlier draft of this same idea that
assumed events that don't exist in `dsh`.
## Troubleshooting
**Turn 1's tool list came back empty.** `whitelistedTools` matches on the
exact registered tool name — these differ per harness install and per other
plugins you have active. Check the real names with
`dsh --profile <name> --dump-config`, or open the Trajectory tab in the web
UI for a session and look at the Tools panel on "Initial System Prompt". An
early draft of this plugin shipped with guessed names (`read_file`,
`list_dir`, `search_files`) that don't exist in the real harness — the
whitelist silently matched nothing and pruned every tool.
**Plugin doesn't seem to load / no `minimal-anchor` entry in `--dump-config`.**
Being a listed `dependency` of the profile (e.g. after `dsh plugin add`) is
not enough — the package also needs to be in that profile's
`dsh.profile.bundles` list (see [Install](#install)) before its `dsh.bundle`
patch gets applied.
**Loader crashes with `Cannot read properties of undefined (reading 'validate')`
on a fork.** A Cordis plugin's exported `Config` must be a schemastery
schema (`z.object({...})`), not a plain object — the loader calls
`.validate` on whatever `Config` exports.
## Development
```bash
npm install
npm run typecheck
npm test
```
Verified against a real local `dsh` boot (not just types): installed into a
scratch profile, patched in, and run against a live model — the outbound
request on turn 1 carried exactly the whitelisted tools and the preamble
text, and turn 2 carried the full toolset with no preamble.
## License
MIT
Install
dsh plugin --profile web add github:rand0wn/dsh-minimal-anchor
Profile: web
With the hub plugin installed, ask your agent to install it by name — it resolves the same plan shown here.
dsh plugin --profile web add github:stvlynn/dsh.fish#path:packages/dsh-plugin-hub
install dsh-minimal-anchor from the hub
- 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.