Skip to content
dsh.fish
Bundle

dsh-subagent-cwd

Enhanced subagent delegation tools for DeepSeek Harness with per-call cwd control: everything in dsh-subagent-tools plus a cwd parameter, shipped with the two in-process provider patches it requires.

Source
lynx-gt
stars
3 stars
License
MIT
Updated
Updated 8 days ago

Readme

# dsh-subagent-cwd

Enhanced subagent delegation tools for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (dsh)
**with per-call working-directory control**.

Everything in [dsh-subagent-tools](https://github.com/lynx-gt/dsh-subagent-tools) (per-call model / provider /
persona / toolFilter overrides, `@preset:` references, `provider/model` composite ids) **plus** a per-call
`cwd` parameter — shipped with the two small provider patches that make `cwd` actually work.

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

[![awesome · DSH plugin](https://awesome-dsh-plugin.com/badge.svg)](https://awesome-dsh-plugin.com)

## Choose one of the two packages — not both

| Package | Per-call model/provider/persona/toolFilter | `@preset:` | `cwd` | Patches |
|---|---|---|---|---|
| **dsh-subagent-tools** | ✅ | ✅ | ❌ | **none (bundle only)** |
| **dsh-subagent-cwd** (this) | ✅ | ✅ | ✅ | 2 provider patches |

Install **either** one. Both expose the same tool surface (`subagent` / `subagent_fork`) and colliding
installations would fight over the tool names.

## Why cwd needs patches (and the other features don't)

`SubagentStartRequest` has **no cwd field**, and the in-process drive layer builds child session meta purely
from `childSessionMeta(parent, ...)` — a per-call cwd is never forwarded. Two code paths create in-process
children, and **both** must be patched or you hit the classic trap where the foreground path honors `cwd`
while the background path silently ignores it:

| Path | Package to patch | File |
|---|---|---|
| Foreground (one-shot) | `@deepseek-ai/dsh-subagent-in-process-driver` | `lib/index.js` |
| Background (continuable) | `@deepseek-ai/dsh-subagent` | **`lib/index.js` (the BUNDLE — not `lib/types/continuation.js`!)** |

The second one is a bundle trap: `package.json` main/exports point at `lib/index.js`, which contains an
inline copy of the continuation manager. Patching the source-shaped `lib/types/continuation.js` has **no
effect** — always patch and verify the bundle.

## persona × preset coexistence (patch v2)

When `preset` and `persona` are given TOGETHER (e.g.
`subagent(preset: "@preset:router-standard", persona: "@preset:翻译员")`), the old patch registered the
per-call persona as the `deployment:persona` section, which router-style presets then deleted by name on
every assemble (`applyPersona` strips every section whose name matches /persona/i) — the role persona was
silently lost.

Patch v2 (install.ps1 hunk 3 / hunk 5, three-state: raw / old-patch / new-patch all upgrade) instead:
- skips the `deployment:persona` registration when preset AND persona are both given;
- registers the per-call persona AFTER recompose as **`delegation:role`** (order 1, no "persona" in the
  name), which the router's filter cannot reach — so the preset's own reasoning persona and the delegated
  role persona COEXIST.

Passing only `persona` (no `preset`) keeps the old behavior: plain `deployment:persona` shadowing.

The patch v2 hunks (3–5) are currently implemented **only in `install.ps1` (Windows)**.
The POSIX `install.sh` preset hunks will be added in a follow-up; on Linux the `preset`
parameter is accepted but does not recompose until that lands.

### ⚠️ Limitation: persona is lost under router-style presets

Even with patch v2 in place, when a subagent/member uses a **router-style preset** (e.g.
`@preset:dsh-router-standard`), the per-call `persona` still does NOT reach the system
prompt. This is a mechanism-level behavior of router-style presets: their
`system-prompt`/`assemble` hook deletes every section whose name matches /persona/i and
injects the preset's own dynamic persona. As a result only the preset's built-in dynamic
persona takes effect; your custom role persona is silently dropped.

Workarounds:
- a) Use a non-router-style preset (e.g. `standard`) so the `persona` parameter is honored;
- b) Inject the role definition through the **message layer** instead (first-turn prompt
  prefix / task-message prefix, e.g. the stitching mechanism used by agent-teams), which
  does not depend on cwd;
- c) Or accept that the `persona` parameter is a no-op under router-style presets (the
  preset's own dynamic persona takes over).

## Installation

```sh
# 1. install the plugin (npm / git / local)
dsh plugin --profile web add dsh-subagent-cwd

# 2. apply the two provider patches (required for `cwd`)
powershell -ExecutionPolicy Bypass -File patches\install.ps1    # Windows
# or: ./patches/install.sh                                       # POSIX

# 3. Web sessions: also run the preset adapter (see below)
powershell -ExecutionPolicy Bypass -File install-preset.ps1     # Windows
# or: ./install-preset.sh                                       # POSIX
```

Restart `dsh --profile web` and start a NEW session.

### Web sessions need the preset adapter

In the **web** profile, agent tools are provided by the mounted agent **preset**
(the default `standard` preset composes `subagent` / `subagent_fork` pointing at
`@deepseek-ai/dsh-tool-subagent`), **not** by the host plane — a bundle patch is
invisible to Web sessions. `install-preset.ps1` / `install-preset.sh` copies
`standard` to `$DSH_HOME/.agent-presets/standard-plus`, rewrites its delegation
rows to point at this package, and switches the default preset. Presets are read
at session creation, so you must start a NEW session (live sessions cannot
switch). Revert: pick `standard` in the UI (General > Agent preset) and delete
`standard-plus`.

> `headless` and other non-web profiles do not need the preset adapter.

### Upgrading dsh

A dsh upgrade rewrites `node_modules` and **wipes both patches**. After every upgrade:

```sh
# re-run the installer (it is idempotent; it also detects version-mismatched anchors)
powershell -ExecutionPolicy Bypass -File patches\install.ps1
```

If the installer reports "anchor not found", the target packages changed shape — check for a new
dsh-subagent-cwd release or file an issue.

### Uninstall

```sh
powershell -ExecutionPolicy Bypass -File patches\uninstall.ps1   # Windows
# or: ./patches/uninstall.sh                                     # POSIX
dsh plugin --profile web remove dsh-subagent-cwd
```

## Example

```
Let a subagent work in a directory without the repo's AGENTS.md injected:
  subagent(description="Summarize this file", prompt="...", cwd="D:\\projects\\scratch\\notes")
```

## Design

- **The tool surface is a bundle.** The shipped `tool-subagent` /
  `tool-subagent-fork` rows are disabled and replaced by this package's rows;
  no official package file is modified for the tool surface itself.
- **`cwd` is the one capability that cannot stay a bundle-only feature.**
  `SubagentStartRequest` has no cwd field, so a per-call cwd must be forwarded
  by the in-process subagent providers. That needs the two small patches (one
  hunk each) in `patches/` — idempotent, backed up on first run, and
  `node --check`-verified. This is the entire reason this package exists
  separately from `dsh-subagent-tools`.
- **Version contract:** `peerDependencies` pin the public dsh packages
  (`0.1.1-rc.1`). The patches target the same version; a dsh upgrade rewrites
  the dsh installation's `node_modules` and **wipes both patches** — re-run
  `patches/install.ps1` / `install.sh` after every upgrade (see *Upgrading dsh*).
  The bundle itself lives in the profile's own `node_modules` and survives an
  upgrade, but a changed public API will make `peerDependencies` refuse to load.

## Verified

Tested against a stock dsh `0.1.1-rc.1` install on Windows (headless + web):

- Everything `dsh-subagent-tools` verifies (per-call model/provider/persona/
  toolFilter, `@preset:`, `presetHints`) ✅
- **`cwd` on the foreground path** ✅ — the child's `pwd` and its sandbox
  workspace both switch to the requested directory
- **`cwd` on the continuable (background) path** ✅ — background children honor
  the same directory (the classic "foreground works, background silently
  ignores cwd" trap does not occur)
- `toolFilter` scoping ✅
- `patches/install.ps1` and `patches/uninstall.ps1` round-trip ✅ (backup,
  apply, `node --check`, restore, re-verify)

## Limitations

- **Patches target rc.1 (0.1.1-rc.1).** The two patches match exact anchors in
  the rc.1 official sources; if a future dsh upgrade moves the anchors, re-run
  `patches/install.ps1` — "anchor not found" means the patches need a new
  adaptation. Since rc.1, the official driver natively consumes per-call
  `persona` / `toolFilter` (`applyChildComposition` reads `request.persona`);
  the patches only cover what the official code still lacks: per-call `cwd`
  forwarding and per-call `preset` recompose.
- **`@preset:` depends on the local preset layout** — same caveat as
  `dsh-subagent-tools`.
- **Web sessions need the preset adapter** (`install-preset.ps1`) for the same
  reason as `dsh-subagent-tools`.

## License

MIT

Install

dsh plugin --profile web add github:lynx-gt/dsh-subagent-cwd

Profile: web

  • This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.
Source