dsh-root-planner-preset 1.0.1 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/CONTRIBUTING.md +92 -0
- package/.github/workflows/verify.yml +51 -0
- package/INSTALL.md +20 -10
- package/README.md +74 -45
- package/README.zh.md +105 -0
- package/build-patch.mjs +4 -25
- package/child-tools.mjs +56 -0
- package/cordis.patch.yml +1 -3
- package/docs/DEVELOPMENT.md +27 -2
- package/package.json +5 -2
- package/persona.txt +1 -1
- package/verify-patch.mjs +58 -31
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Thanks for looking. This repository is meant to be maintained by more than its
|
|
4
|
+
author — the license is [MIT](../LICENSE), and issues and pull requests are open
|
|
5
|
+
to anyone.
|
|
6
|
+
## The one thing to know first
|
|
7
|
+
|
|
8
|
+
**`cordis.patch.yml` is generated. Do not edit it by hand.**
|
|
9
|
+
|
|
10
|
+
It is produced from `persona.txt` by `build-patch.mjs`, and it has to be indented
|
|
11
|
+
into a YAML block scalar exactly — a single mis-indented line silently truncates
|
|
12
|
+
the ~6 KB persona, which is very hard to spot in review and easy to introduce by
|
|
13
|
+
hand. That is the whole reason the generator exists.
|
|
14
|
+
|
|
15
|
+
So to change what the agent is told:
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
$EDITOR persona.txt
|
|
19
|
+
node build-patch.mjs # regenerates cordis.patch.yml
|
|
20
|
+
node verify-patch.mjs # must print ALL CHECKS PASSED
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Commit **both** files. CI re-runs the generator and fails if the committed patch
|
|
24
|
+
does not match `persona.txt` byte for byte, so forgetting the second command is
|
|
25
|
+
caught for you.
|
|
26
|
+
|
|
27
|
+
## Setup
|
|
28
|
+
|
|
29
|
+
The bundle itself ships no dependencies. The repository's scripts need one YAML
|
|
30
|
+
parser, which `lib-yaml.mjs` resolves from your own DSH installation — or from a
|
|
31
|
+
local install:
|
|
32
|
+
|
|
33
|
+
```sh
|
|
34
|
+
npm ci # installs js-yaml, the only devDependency
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## What the checks mean
|
|
38
|
+
|
|
39
|
+
`node verify-patch.mjs` asserts, among other things:
|
|
40
|
+
|
|
41
|
+
- the patch parses as loader YAML (including the `!!js` scalars, read as opaque);
|
|
42
|
+
- the persona parsed out of the patch is **byte-identical** to `persona.txt`;
|
|
43
|
+
- both delegation tools publish `subagent` / `subagent_fork`;
|
|
44
|
+
- both `toolFilter.allow` masks exclude every mutation entry point
|
|
45
|
+
(`write`, `edit`, `ops_replace`, `ops_bash`, `pwsh`) and name **only tools that
|
|
46
|
+
exist** — a name that is not a registered global tool makes
|
|
47
|
+
`tools.restrict()` throw the first time a subagent is created.
|
|
48
|
+
|
|
49
|
+
That last point is the most likely thing to need changing on your machine: the
|
|
50
|
+
mask was written against the author's profile. If your profile registers a
|
|
51
|
+
different set of global tools, the mask is what has to adapt — edit the list near
|
|
52
|
+
the bottom of `verify-patch.mjs` in the same commit.
|
|
53
|
+
|
|
54
|
+
`node verify-depth-override.mjs` checks the delegation depth override. Depth is a
|
|
55
|
+
**Host setting, not part of this bundle**, so on a machine that never set it the
|
|
56
|
+
script prints `SKIP` and explains why. That is a pass, not a failure.
|
|
57
|
+
|
|
58
|
+
## Most valuable contributions, in order
|
|
59
|
+
|
|
60
|
+
1. **Bug reports with real error text**, plus your DSH version and which global
|
|
61
|
+
tools your profile registers. The `Not verified` section of the
|
|
62
|
+
[README](../README.md) names exactly where this matters: no subagent has been
|
|
63
|
+
spawned under this preset in the author's environment to date.
|
|
64
|
+
2. **Mask fixes for other environments** — see above.
|
|
65
|
+
3. **Persona improvements** — the text itself, when you have an observed failure
|
|
66
|
+
it would have prevented.
|
|
67
|
+
4. **Documentation and translations** — [README.zh.md](../README.zh.md) and
|
|
68
|
+
[INSTALL.md](../INSTALL.md) especially.
|
|
69
|
+
|
|
70
|
+
## Ground rules
|
|
71
|
+
|
|
72
|
+
- **One concern per pull request.** A persona change and a mask change are two
|
|
73
|
+
pull requests unless one requires the other.
|
|
74
|
+
- Say which preset you tested with, and on which platform.
|
|
75
|
+
- Do not weaken the read-only guarantee. `write`, `edit`, `ops_replace`,
|
|
76
|
+
`ops_bash` and `pwsh` must stay out of the child mask; that property is the
|
|
77
|
+
point of the preset, so a change that removes it will be declined.
|
|
78
|
+
- Keep `main` linear — the branch protection refuses merge commits, so rebase
|
|
79
|
+
rather than merge.
|
|
80
|
+
|
|
81
|
+
## How `main` is protected
|
|
82
|
+
|
|
83
|
+
`main` requires the `verify` status check (strict: the branch must be up to date),
|
|
84
|
+
refuses force pushes and deletions, requires linear history, and requires
|
|
85
|
+
conversations to be resolved. The repository owner can bypass these for a direct
|
|
86
|
+
push in an emergency; **collaborators cannot** — send a pull request and let CI
|
|
87
|
+
run.
|
|
88
|
+
|
|
89
|
+
If you are added as a collaborator and want to be able to merge your own pull
|
|
90
|
+
requests without waiting for the owner, say so: review requirements are off
|
|
91
|
+
deliberately, because a single-maintainer repository that requires an approval it
|
|
92
|
+
cannot get would deadlock.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Verifies the bundle on every push and pull request.
|
|
2
|
+
#
|
|
3
|
+
# Two failure modes this exists to catch, both of which are silent locally:
|
|
4
|
+
#
|
|
5
|
+
# 1. `cordis.patch.yml` is generated from `persona.txt`. Someone edits the
|
|
6
|
+
# generated file by hand (or edits persona.txt and forgets to regenerate), and
|
|
7
|
+
# the persona the preset actually ships no longer matches the source. The
|
|
8
|
+
# generator is deterministic, so re-running it and diffing the result catches
|
|
9
|
+
# exactly that drift.
|
|
10
|
+
# 2. A change to the child tool mask names a tool that is not a registered global
|
|
11
|
+
# tool. `tools.restrict()` throws on an unknown name at child-creation time, so
|
|
12
|
+
# this only surfaces the first time a subagent is actually spawned — never in a
|
|
13
|
+
# local syntax check.
|
|
14
|
+
#
|
|
15
|
+
# The bundle itself ships no dependencies; `js-yaml` is a devDependency of this
|
|
16
|
+
# repository only, and `lib-yaml.mjs` picks it up from the checkout.
|
|
17
|
+
|
|
18
|
+
name: verify
|
|
19
|
+
|
|
20
|
+
on:
|
|
21
|
+
push:
|
|
22
|
+
branches: [main]
|
|
23
|
+
pull_request:
|
|
24
|
+
workflow_dispatch:
|
|
25
|
+
|
|
26
|
+
permissions:
|
|
27
|
+
contents: read
|
|
28
|
+
|
|
29
|
+
jobs:
|
|
30
|
+
verify:
|
|
31
|
+
runs-on: ubuntu-latest
|
|
32
|
+
steps:
|
|
33
|
+
- uses: actions/checkout@v4
|
|
34
|
+
|
|
35
|
+
- uses: actions/setup-node@v4
|
|
36
|
+
with:
|
|
37
|
+
node-version: '22'
|
|
38
|
+
|
|
39
|
+
- name: Install the local YAML parser
|
|
40
|
+
run: npm ci --ignore-scripts
|
|
41
|
+
|
|
42
|
+
- name: The committed patch matches persona.txt
|
|
43
|
+
run: |
|
|
44
|
+
node build-patch.mjs
|
|
45
|
+
if ! git diff --exit-code -- cordis.patch.yml; then
|
|
46
|
+
echo "::error file=cordis.patch.yml::cordis.patch.yml is out of date. Run 'node build-patch.mjs' and commit the result instead of editing it by hand."
|
|
47
|
+
exit 1
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
- name: Patch structure, persona byte-identity and the child tool mask
|
|
51
|
+
run: node verify-patch.mjs
|
package/INSTALL.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
面向**接收方**。本包是一个 DSH agent 预设(bundle),不改动 DSH 本体。
|
|
4
4
|
|
|
5
|
-
[
|
|
5
|
+
[English README](README.md) | [中文说明](README.zh.md) | [维护者文档](docs/DEVELOPMENT.md)
|
|
6
6
|
|
|
7
7
|
## 一、前置条件
|
|
8
8
|
|
|
@@ -32,27 +32,35 @@ web_search web_fetch
|
|
|
32
32
|
|
|
33
33
|
## 二、安装
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
四种方式任选,推荐第 1 种。
|
|
36
36
|
|
|
37
|
-
### 方式 1
|
|
37
|
+
### 方式 1:npm(推荐)
|
|
38
38
|
|
|
39
39
|
```sh
|
|
40
|
-
dsh plugin --profile web add
|
|
40
|
+
dsh plugin --profile web add dsh-root-planner-preset
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
已发布在 npm:<https://www.npmjs.com/package/dsh-root-planner-preset>。这是**预构建安装**,跳过 pnpm 的构建授权步骤,最省事。
|
|
44
|
+
|
|
45
|
+
> 也可以钉版本:`dsh plugin --profile web add dsh-root-planner-preset@1.0.0`
|
|
46
|
+
|
|
47
|
+
### 方式 2:GitHub 源码
|
|
48
|
+
|
|
49
|
+
```sh
|
|
50
|
+
dsh plugin --profile web add github:XiaoOu2024/dsh-root-planner-preset
|
|
51
|
+
```
|
|
44
52
|
|
|
45
|
-
>
|
|
53
|
+
> 也可以指定分支或 tag:`github:XiaoOu2024/dsh-root-planner-preset#v1.0.0`
|
|
46
54
|
|
|
47
|
-
### 方式
|
|
55
|
+
### 方式 3:让 DSH 里的 agent 装
|
|
48
56
|
|
|
49
57
|
对 agent 说:
|
|
50
58
|
|
|
51
|
-
> 用 `plugin_manager` 的 `install_bundle`,target 填 `github:XiaoOu2024/dsh-root-planner-preset
|
|
59
|
+
> 用 `plugin_manager` 的 `install_bundle`,target 填 `dsh-root-planner-preset`(或 `github:XiaoOu2024/dsh-root-planner-preset`)
|
|
52
60
|
|
|
53
61
|
注意:这个工具调用需要 **Full access** 权限或当次授权。
|
|
54
62
|
|
|
55
|
-
### 方式
|
|
63
|
+
### 方式 4:先克隆再按路径装
|
|
56
64
|
|
|
57
65
|
```sh
|
|
58
66
|
git clone https://github.com/XiaoOu2024/dsh-root-planner-preset
|
|
@@ -60,6 +68,8 @@ git clone https://github.com/XiaoOu2024/dsh-root-planner-preset
|
|
|
60
68
|
|
|
61
69
|
然后让 agent 用 `install_bundle`,target 填**克隆下来的绝对路径**。
|
|
62
70
|
|
|
71
|
+
装完重启 `dsh web`。
|
|
72
|
+
|
|
63
73
|
### ❌ 不适用:插件市场界面
|
|
64
74
|
|
|
65
75
|
**「设置 → 插件市场」搜不到本包,属正常。** dsh-market 只允许安装 `awesome-dsh-plugin` 精选列表内的来源,本包不在其中。请用上面三种方式。
|
|
@@ -97,7 +107,7 @@ git clone https://github.com/XiaoOu2024/dsh-root-planner-preset
|
|
|
97
107
|
|---|---|---|
|
|
98
108
|
| 预设列表里**没有 Root Planner** | bundle 没装上,或预设行激活失败(会带诊断留在名册上) | 看 DSH 的插件/加载诊断;多半是第 1 条前置不满足——本包的补丁引用了随包预设里不存在的工具行 |
|
|
99
109
|
| DSH **启动失败**(进程直接退出) | DSH 启动是全有全无,一个插件加载失败整个进程就退出 | 用插件市场的「**调整插件**」恢复界面:DSH 点名的插件会标红并默认取消勾选,勾好再启动。找不到该界面时,手工编辑 profile 的 `cordis.patch.yml` 把对应行设为 `disabled: true` |
|
|
100
|
-
| 子代理创建失败,报 **`tools.restrict() names unknown global tool …`** |
|
|
110
|
+
| 子代理创建失败,报 **`tools.restrict() names unknown global tool …`** | 白名单里有**不是全局工具**的名字。注意有两种情况:①你的环境根本没注册该工具;②它是**agent 作用域**工具(如 `subagent`,它注册在每个被组合 Agent 自身的作用域里),而 `toolFilter` **只接受全局工具名** | 报错里会列出该环境全部的全局工具名,照着它删掉白名单里那几个不存在的名字。白名单在 `cordis.patch.yml` 的两个 `toolFilter.allow` 处(或在 `child-tools.mjs` 里改后重跑生成器),改完必须重开会话 |
|
|
101
111
|
| 委派立刻返回**深度错误** | `maxDepth` 没设或设得太小 | 见第三节,设为 `2` |
|
|
102
112
|
| 子代理**什么都不能读** | 白名单过窄(比如你删过头了) | 至少保留 `read`、`glob`、`grep`、`ops_inspect_local_file`、`ops_glob`、`ops_grep` |
|
|
103
113
|
| 子代理**还是能改文件** | 白名单被改坏了 | 确认 `cordis.patch.yml` 里两处 `toolFilter.allow` 都**不含** `write`、`edit`、`ops_replace`、`ops_bash`、`pwsh`;改完必须重开会话 |
|
package/README.md
CHANGED
|
@@ -1,82 +1,111 @@
|
|
|
1
1
|
# dsh-root-planner-preset
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
An agent preset for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (DSH) that **separates reading from deciding**.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
The root agent's context holds task analysis, planning and decisions; file reading and information queries go to subagents that return summaries only. Creating, modifying and deleting files stays with the root agent — subagents do not even have the tools.
|
|
6
6
|
|
|
7
|
-
>
|
|
7
|
+
> One agent in the tree has hands. Every other agent has only eyes.
|
|
8
8
|
|
|
9
|
-
[
|
|
9
|
+
[中文说明](README.zh.md) | [Install](INSTALL.md) | [Maintainer notes](docs/DEVELOPMENT.md)
|
|
10
10
|
|
|
11
|
-
##
|
|
11
|
+
## The problem it solves
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
On a long task a coding agent's context fills up with raw file contents, and it forgets the goal, loses the plan, and starts repeating work. The usual answer — "delegate more to subagents" — does not hold on its own: a subagent will still edit files on its way past, and work gets passed further down without limit.
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
This preset turns both of those into structural constraints rather than good intentions:
|
|
16
16
|
|
|
17
|
-
|
|
|
17
|
+
| Requirement | Mechanism | Strength |
|
|
18
18
|
|---|---|---|
|
|
19
|
-
|
|
|
20
|
-
|
|
|
21
|
-
|
|
|
22
|
-
|
|
|
23
|
-
|
|
|
19
|
+
| Subagents **cannot** create, modify or delete files | A `toolFilter.allow` list on both delegation tools, without `write`, `edit`, `ops_replace` | **Structural**: an absent tool cannot be called |
|
|
20
|
+
| Subagents **have no shell** to work around it | The same list, without `ops_bash`, `pwsh` | **Structural** |
|
|
21
|
+
| Subagents cannot delegate further | The same list cannot name the spawn tool, *and* the runtime depth cap | **Structural**, twice over — see below |
|
|
22
|
+
| The root agent owns every mutation | Persona rules (the mask applies to children only) | Prompt-level |
|
|
23
|
+
| The root agent delegates reading by default | Persona rules | Prompt-level |
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
The mask is an **allow** list, not a deny list: a tool added by a future plugin is invisible to subagents until it is added here.
|
|
26
26
|
|
|
27
|
-
##
|
|
27
|
+
## How it works
|
|
28
28
|
|
|
29
29
|
```
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
│ └── 孙代理 (depth 2) ← 只读;叶子
|
|
33
|
-
└── 子代理 (depth 1)
|
|
30
|
+
root agent <- the only one with write / edit / shell
|
|
31
|
+
└── subagent <- read-only, and cannot delegate further
|
|
34
32
|
```
|
|
35
33
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
34
|
+
The tree is two levels, enforced twice over. The runtime depth cap does it, and so
|
|
35
|
+
does the mask: `subagent` is mounted inside each composed Agent's own scope rather
|
|
36
|
+
than globally, while a `toolFilter` accepts global tool names only — so a child
|
|
37
|
+
cannot even name it. An earlier version of this bundle did list `subagent` in the
|
|
38
|
+
mask, and **every subagent creation failed** with
|
|
39
|
+
`tools.restrict() names unknown global tool "subagent"`. Raising the depth setting
|
|
40
|
+
therefore does not re-open delegation; there is no tool left to call.
|
|
39
41
|
|
|
40
|
-
|
|
42
|
+
- **Root agent**: plans first (goal, success criteria, constraints, ordered task list), delegates reading, searching and locating by default, and reads directly only for one known file, a short targeted range, or to check a claim a subagent made.
|
|
43
|
+
- **Subagent**: reads, locates and summarizes. On finding that a file **needs changing** it does not act; it reports the exact change — target path, location (`file:line`), current content, proposed replacement, and why.
|
|
44
|
+
- **The root agent decides** on that proposal and then makes the change itself.
|
|
41
45
|
|
|
42
|
-
|
|
46
|
+
How does a subagent know it is a subagent? DSH force-inserts the sentence `You are a delegated subagent: …` into every subagent's prompt, injected by the runtime and impossible to remove — while a preset persona has **no** `{{role}}`-style variable available. So the persona states a **test** instead: read your own prompt for that sentence. One persona then serves both roles, and parent and child read the same text, which is what prevents the drift where a parent believes a child is constrained and the child never saw it.
|
|
47
|
+
|
|
48
|
+
## Quick start
|
|
49
|
+
|
|
50
|
+
Install from npm (prebuilt, no build-approval step):
|
|
51
|
+
|
|
52
|
+
```sh
|
|
53
|
+
dsh plugin --profile web add dsh-root-planner-preset
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Or straight from GitHub source:
|
|
43
57
|
|
|
44
58
|
```sh
|
|
45
59
|
dsh plugin --profile web add github:XiaoOu2024/dsh-root-planner-preset
|
|
46
60
|
```
|
|
47
61
|
|
|
48
|
-
|
|
62
|
+
Then **one required step**: set **Settings → Subagents → depth limit** to **`2`**. (This is the Host's `subagent` settings section — global, and **not** part of the bundle. Setting it to `0` disables all delegation and breaks the design.)
|
|
49
63
|
|
|
50
|
-
|
|
64
|
+
Then **start a new session** and pick **Root Planner** in the preset selector. Full steps and troubleshooting are in [INSTALL.md](INSTALL.md).
|
|
51
65
|
|
|
52
|
-
##
|
|
66
|
+
## When it fits, and when it does not
|
|
53
67
|
|
|
54
|
-
|
|
68
|
+
**Fits**: long research-heavy tasks across many files, reverse engineering, code review — anything read-heavy, or where a change must be carefully decided.
|
|
55
69
|
|
|
56
|
-
|
|
70
|
+
**Does not fit**:
|
|
57
71
|
|
|
58
|
-
-
|
|
59
|
-
-
|
|
60
|
-
-
|
|
72
|
+
- changing one file quickly — the extra delegation layer is pure overhead;
|
|
73
|
+
- wanting subagents to **write code in parallel** — this preset forbids that on purpose; use the `standard` preset instead;
|
|
74
|
+
- an environment missing the DSH tooling listed in [INSTALL.md](INSTALL.md).
|
|
61
75
|
|
|
62
|
-
##
|
|
76
|
+
## Repository contents
|
|
63
77
|
|
|
64
|
-
|
|
|
78
|
+
| File | Purpose |
|
|
65
79
|
|---|---|
|
|
66
|
-
| `cordis.patch.yml` |
|
|
67
|
-
| `persona.txt` |
|
|
68
|
-
| `build-patch.mjs` |
|
|
69
|
-
| `verify-patch.mjs` |
|
|
70
|
-
| `verify-depth-override.mjs` |
|
|
71
|
-
| `lib-yaml.mjs` |
|
|
72
|
-
| `docs/DEVELOPMENT.md` |
|
|
80
|
+
| `cordis.patch.yml` | The preset declaration (**generated — do not hand-edit**) |
|
|
81
|
+
| `persona.txt` | The persona prose; **edit this one** |
|
|
82
|
+
| `build-patch.mjs` | Generates `cordis.patch.yml` from `persona.txt` |
|
|
83
|
+
| `verify-patch.mjs` | Checks patch structure and the tool mask, and asserts byte identity of the persona |
|
|
84
|
+
| `verify-depth-override.mjs` | Checks that the depth override reached the composed profile |
|
|
85
|
+
| `lib-yaml.mjs` | Resolves a YAML parser from the local DSH install, so this package has **no dependencies** |
|
|
86
|
+
| `docs/DEVELOPMENT.md` | Maintainer notes: design trade-offs, mechanics, verified/unverified boundaries |
|
|
87
|
+
|
|
88
|
+
## Verified / not verified
|
|
89
|
+
|
|
90
|
+
**Verified**: the patch parses as loader YAML; the parsed persona is **byte-identical** to `persona.txt`; both delegation masks exclude every mutation entry point and name only tools the runtime reports as global; a clean checkout passes verification (`.gitattributes` pins LF, without which a Windows clone breaks the byte-identity check); the depth override reached the composed profile.
|
|
91
|
+
|
|
92
|
+
**Fixed after a real failure**: a subagent spawn on a live `root-planner` session failed with `tools.restrict() names unknown global tool "subagent"`. The cause was a design error in this bundle, not a misconfiguration — `subagent` is registered inside each composed Agent's own scope, and a `toolFilter` takes global names only. The mask no longer names it, `verify-patch.mjs` now asserts its absence by intent, and the mask is defined once in `child-tools.mjs` so the generator and the verifier cannot drift apart.
|
|
93
|
+
|
|
94
|
+
**Still not verified**: no subagent has been spawned successfully under this preset yet — the failure above is the only end-to-end evidence there is. The global-tool inventory in `verify-patch.mjs` was transcribed from that runtime error; if your profile registers a different set, the mask is what has to adapt.
|
|
95
|
+
|
|
96
|
+
## Contributing
|
|
97
|
+
|
|
98
|
+
Issues and pull requests are welcome — this repository is meant to be maintained by more than its author. The license is [MIT](LICENSE), so forking, modifying and redistributing are all permitted. **[CONTRIBUTING.md](.github/CONTRIBUTING.md) has the workflow, the checks, and the ground rules** — read it before changing the persona.
|
|
73
99
|
|
|
74
|
-
|
|
100
|
+
Useful contributions, roughly in order of value:
|
|
75
101
|
|
|
76
|
-
|
|
102
|
+
- **Report what broke**, with the error text and your DSH version. The unverified area above is exactly where real reports matter most.
|
|
103
|
+
- **Report a mask mismatch** on your platform: which global tools your profile registers and which name the mask got wrong.
|
|
104
|
+
- **Improve the persona** (`persona.txt`) — but run `node build-patch.mjs && node verify-patch.mjs` and include the result, since `cordis.patch.yml` is generated and a hand-edited one will be rejected.
|
|
105
|
+
- **Translate or fix the docs** — [README.zh.md](README.zh.md) and [INSTALL.md](INSTALL.md).
|
|
77
106
|
|
|
78
|
-
|
|
107
|
+
Please keep a change to one concern per pull request, and say which preset it was tested with.
|
|
79
108
|
|
|
80
|
-
##
|
|
109
|
+
## License
|
|
81
110
|
|
|
82
111
|
[MIT](LICENSE)
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# dsh-root-planner-preset
|
|
2
|
+
|
|
3
|
+
一个面向 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(DSH)的 agent 预设:**把「读书」和「决策」分开**。
|
|
4
|
+
|
|
5
|
+
根 agent 的上下文只装任务分析、规划与决策;文件阅读、信息查询交给子代理,子代理只回摘要。而**创建、修改、删除文件只能由根 agent 执行**——子代理连工具都没有。
|
|
6
|
+
|
|
7
|
+
> 一句话:让整棵树里只有一个 agent 有手,其余都只有眼睛。
|
|
8
|
+
|
|
9
|
+
[English](README.md) | [安装步骤](INSTALL.md) | [维护者文档](docs/DEVELOPMENT.md)
|
|
10
|
+
|
|
11
|
+
## 它解决什么问题
|
|
12
|
+
|
|
13
|
+
用 coding agent 干长任务,上下文很快被原始文件内容塞满,于是忘了目标、丢了计划、开始重复劳动。常见对策是「让 agent 多派子代理」,但光靠提示词约束不住——子代理照样会顺手改文件,任务也会被无限往下转派。
|
|
14
|
+
|
|
15
|
+
这个预设把两件事做成了结构约束,而不是靠自觉:
|
|
16
|
+
|
|
17
|
+
| 要求 | 机制 | 强度 |
|
|
18
|
+
|---|---|---|
|
|
19
|
+
| 子代理**不能**创建/修改/删除文件 | 两个委派工具上的 `toolFilter.allow` 白名单,不含 `write`、`edit`、`ops_replace` | **结构性**:工具不存在,调不到 |
|
|
20
|
+
| 子代理**没有** shell 可绕过 | 同一白名单,不含 `ops_bash`、`pwsh` | **结构性** |
|
|
21
|
+
| 子代理不能再往下派 | 同一白名单无法写上 spawn 工具名,**且**运行时深度上限 | **双重结构性**,见下 |
|
|
22
|
+
| 根 agent 独揽所有修改 | 人设规则(白名单只作用于子级) | 提示词级 |
|
|
23
|
+
| 根 agent 默认委派阅读 | 人设规则 | 提示词级 |
|
|
24
|
+
|
|
25
|
+
白名单是 **allow 而非 deny**:将来某个插件新增的工具,对子代理默认不可见。
|
|
26
|
+
|
|
27
|
+
## 工作方式
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
根 agent ← 只有它有 write / edit / shell
|
|
31
|
+
└── 子代理 ← 只读,且不能再往下派
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
树只有两层,而且是**双重强制**的:运行时深度上限挡一层,白名单再挡一层。因为 `subagent` 是注册在每个被组合 Agent **自身作用域**里的,而 `toolFilter` 只接受全局工具名——子代理连它的名字都写不上。本包早期版本确实把 `subagent` 写进了白名单,结果是**每一次子代理创建都失败**,报 `tools.restrict() names unknown global tool "subagent"`。所以调高深度设置也不会重新打开委派:已经没有可调用的工具了。
|
|
35
|
+
|
|
36
|
+
- **根 agent**:先立计划(目标、成功判据、约束、任务清单),把阅读/搜索/定位默认派出去,自己只在「单个已知文件、一小段范围、或核实子代理的某个断言」时才亲自读。
|
|
37
|
+
- **子代理**:只读、只定位、只总结。发现**需要改文件**时不动手,而是回报精确的改动方案——目标路径、位置(file:line)、当前内容、建议替换、理由。
|
|
38
|
+
- **根 agent 收到方案后决策**,然后亲自落盘。
|
|
39
|
+
|
|
40
|
+
子代理怎么知道自己是子代理?DSH 会在每个子代理的提示词里强制插入一句 `You are a delegated subagent: …`,运行时注入、无法移除,而预设人设**没有** `{{role}}` 这类变量可用。所以人设里写的是一条**判定指令**:读自己的提示词里有没有那句话。这让人设只需要一份,父与子读到的是同一份文本,因此不会出现「父以为子被这么约束、子其实没读到」的漂移。
|
|
41
|
+
|
|
42
|
+
## 快速开始
|
|
43
|
+
|
|
44
|
+
推荐从 npm 安装(预构建,免构建授权步骤):
|
|
45
|
+
|
|
46
|
+
```sh
|
|
47
|
+
dsh plugin --profile web add dsh-root-planner-preset
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
或直接从 GitHub 源码安装:
|
|
51
|
+
|
|
52
|
+
```sh
|
|
53
|
+
dsh plugin --profile web add github:XiaoOu2024/dsh-root-planner-preset
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
装完**必须**再做一步:**设置 → 子智能体 → 深度上限 = `2`**。(这是 Host 的 `subagent` 设置节,**全局生效、不属于** bundle;设成 `0` 会禁掉全部委派,设计就废了。)
|
|
57
|
+
|
|
58
|
+
然后**开新会话**,在预设选择器里选 **Root Planner**。完整步骤与故障排查见 [INSTALL.md](INSTALL.md)。
|
|
59
|
+
|
|
60
|
+
## 适合 / 不适合
|
|
61
|
+
|
|
62
|
+
**适合**:需要跨很多文件调研的长任务、逆向分析、代码审查,以及任何「读得多、改得少」或「改动必须被仔细决策」的工作。
|
|
63
|
+
|
|
64
|
+
**不适合**:
|
|
65
|
+
|
|
66
|
+
- 只想快速改一个文件——多一层委派是纯粹的开销;
|
|
67
|
+
- 需要子代理**并行写代码**——这是本预设**故意**禁止的,请改用 `standard` 预设;
|
|
68
|
+
- 环境缺少 DSH 的工具基础(见 [INSTALL.md](INSTALL.md) 的前置条件)。
|
|
69
|
+
|
|
70
|
+
## 仓库内容
|
|
71
|
+
|
|
72
|
+
| 文件 | 说明 |
|
|
73
|
+
|---|---|
|
|
74
|
+
| `cordis.patch.yml` | 预设声明(**由脚本生成,不要手改**) |
|
|
75
|
+
| `persona.txt` | 人设正文,**要改就改这个** |
|
|
76
|
+
| `build-patch.mjs` | 从 `persona.txt` 生成 `cordis.patch.yml` |
|
|
77
|
+
| `verify-patch.mjs` | 校验补丁结构、工具白名单,并断言人设逐字节一致 |
|
|
78
|
+
| `verify-depth-override.mjs` | 校验深度覆盖是否落到合成配置 |
|
|
79
|
+
| `lib-yaml.mjs` | 从本地 DSH 安装里解析 YAML,因此本包**零依赖** |
|
|
80
|
+
| `docs/DEVELOPMENT.md` | 维护者文档:设计取舍、机制细节、已验证/未验证边界 |
|
|
81
|
+
|
|
82
|
+
## 已验证 / 未验证
|
|
83
|
+
|
|
84
|
+
**已验证**:补丁可被 loader 解析;解析出的人设与 `persona.txt` **逐字节相同**;两个委派工具的白名单排除了所有写入入口、且只包含**运行时报告的全局工具**;干净检出后校验通过(`.gitattributes` 已钉死 LF,否则 Windows 克隆会破坏逐字节校验);深度覆盖已落到合成配置。
|
|
85
|
+
|
|
86
|
+
**因一次真实故障而修复**:在跑着 `root-planner` 的会话里创建子代理失败,报 `tools.restrict() names unknown global tool "subagent"`。根因是本包**设计上的错误**,不是配置错误——`subagent` 注册在每个被组合 Agent 自身的作用域里,而 `toolFilter` 只接受全局工具名。现在白名单不再写它,`verify-patch.mjs` 会**按意图断言它必须缺席**,白名单也收敛到 `child-tools.mjs` 单一真源,生成器与校验脚本不可能再各说各话。
|
|
87
|
+
|
|
88
|
+
**仍未验证**:还没有一次**成功**的子代理派出——上面那次失败是目前唯一的端到端证据。`verify-patch.mjs` 里的全局工具清单就是**从那次的运行时报错里抄下来的**;你的 profile 若注册了不同的集合,需要改的是白名单。
|
|
89
|
+
|
|
90
|
+
## 参与维护
|
|
91
|
+
|
|
92
|
+
**欢迎提 issue 和 PR——这个仓库本来就打算由作者之外的人一起维护。** 许可证是 [MIT](LICENSE),fork、修改、再分发都是允许的。**[CONTRIBUTING.md](.github/CONTRIBUTING.md) 写清了流程、校验项和底线要求**,改人设之前请先读它。
|
|
93
|
+
|
|
94
|
+
按价值排序,有用的贡献大概是这样:
|
|
95
|
+
|
|
96
|
+
- **报告你遇到的故障**,带上完整报错和你使用的 DSH 版本。上面「未验证」那段正是最需要真实反馈的地方。
|
|
97
|
+
- **报告工具白名单在你的环境里不匹配**:你的 profile 注册了哪些全局工具、白名单里哪个名字报错了。
|
|
98
|
+
- **改进人设**(`persona.txt`)——但请跑一次 `node build-patch.mjs && node verify-patch.mjs` 并把结果一并附上,因为 `cordis.patch.yml` 是生成文件,手工改过的会被拒。
|
|
99
|
+
- **翻译或修订文档**——[README.md](README.md) 与 [INSTALL.md](INSTALL.md)。
|
|
100
|
+
|
|
101
|
+
请一个 PR 只做一件事,并说明你在哪个预设下验证过。
|
|
102
|
+
|
|
103
|
+
## 许可
|
|
104
|
+
|
|
105
|
+
[MIT](LICENSE)
|
package/build-patch.mjs
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import { readFileSync, writeFileSync } from 'node:fs';
|
|
11
11
|
import { dirname, join } from 'node:path';
|
|
12
12
|
import { fileURLToPath } from 'node:url';
|
|
13
|
+
import { CHILD_TOOLS } from './child-tools.mjs';
|
|
13
14
|
|
|
14
15
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
15
16
|
|
|
@@ -44,32 +45,10 @@ function allowList(names, keyDepth) {
|
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
/**
|
|
47
|
-
* The child mask
|
|
48
|
-
*
|
|
48
|
+
* The child mask lives in `child-tools.mjs` so this generator and
|
|
49
|
+
* `verify-patch.mjs` cannot disagree about it. See that file for why `subagent`
|
|
50
|
+
* cannot appear in the list.
|
|
49
51
|
*/
|
|
50
|
-
const CHILD_TOOLS = [
|
|
51
|
-
'read',
|
|
52
|
-
'glob',
|
|
53
|
-
'grep',
|
|
54
|
-
'ops_inspect_local_file',
|
|
55
|
-
'ops_glob',
|
|
56
|
-
'ops_grep',
|
|
57
|
-
'web_search',
|
|
58
|
-
'web_fetch',
|
|
59
|
-
'read_image',
|
|
60
|
-
'skill',
|
|
61
|
-
'todo_write',
|
|
62
|
-
'ask_user_question',
|
|
63
|
-
'get_goal',
|
|
64
|
-
'create_goal',
|
|
65
|
-
'update_goal',
|
|
66
|
-
'list_agents',
|
|
67
|
-
'send_message',
|
|
68
|
-
'interrupt_agent',
|
|
69
|
-
'subagent',
|
|
70
|
-
'subagent_fork',
|
|
71
|
-
];
|
|
72
|
-
|
|
73
52
|
const persona = readPersona();
|
|
74
53
|
|
|
75
54
|
/** The delegation group is shared by both shapes of child, so the mask is declared once. */
|
package/child-tools.mjs
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The child tool mask — the single source of truth shared by the generator and
|
|
3
|
+
* the verifier, so the two cannot disagree about what a subagent may call.
|
|
4
|
+
*
|
|
5
|
+
* These are the only GLOBAL tools a subagent may call. Everything absent is
|
|
6
|
+
* invisible to the child, including every mutation entry point.
|
|
7
|
+
*
|
|
8
|
+
* `subagent` is deliberately NOT here, and cannot be. With
|
|
9
|
+
* `modelSelectionSettings: true` that tool is registered per agent inside the
|
|
10
|
+
* composed Agent's own scope (`dsh-tool-subagent` mounts it through
|
|
11
|
+
* `installScoped`), and `tools.restrict()` takes a mask of *global* tools only —
|
|
12
|
+
* it throws on a scope-local name exactly as it throws on an unknown one:
|
|
13
|
+
*
|
|
14
|
+
* Error: tools.restrict() names unknown global tool "subagent"; known global
|
|
15
|
+
* tools: ask_user_question, create_goal, edit, … subagent_fork, …
|
|
16
|
+
*
|
|
17
|
+
* `subagent_fork` is registered in the preset's own scope instead, so it IS
|
|
18
|
+
* visible to the mask and stays. Both names were in this list until a real
|
|
19
|
+
* subagent spawn failed, which is exactly the class of mistake no amount of
|
|
20
|
+
* reading the source had caught.
|
|
21
|
+
*/
|
|
22
|
+
export const CHILD_TOOLS = [
|
|
23
|
+
'read',
|
|
24
|
+
'glob',
|
|
25
|
+
'grep',
|
|
26
|
+
'ops_inspect_local_file',
|
|
27
|
+
'ops_glob',
|
|
28
|
+
'ops_grep',
|
|
29
|
+
'web_search',
|
|
30
|
+
'web_fetch',
|
|
31
|
+
'read_image',
|
|
32
|
+
'skill',
|
|
33
|
+
'todo_write',
|
|
34
|
+
'ask_user_question',
|
|
35
|
+
'get_goal',
|
|
36
|
+
'create_goal',
|
|
37
|
+
'update_goal',
|
|
38
|
+
'list_agents',
|
|
39
|
+
'send_message',
|
|
40
|
+
'interrupt_agent',
|
|
41
|
+
'subagent_fork',
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Tools that are agent-scoped rather than global, so a mask can never name them.
|
|
46
|
+
* Kept separate so the verifier can assert their absence by intent rather than
|
|
47
|
+
* by accident.
|
|
48
|
+
*/
|
|
49
|
+
export const SCOPE_LOCAL_TOOLS = ['subagent'];
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The mutation entry points this bundle promises a subagent cannot reach. The
|
|
53
|
+
* verifier asserts each one is absent; `ops_bash` and `bash` are included even
|
|
54
|
+
* when a profile registers neither, because their absence is the property.
|
|
55
|
+
*/
|
|
56
|
+
export const MUTATION_TOOLS = ['write', 'edit', 'ops_replace', 'ops_bash', 'pwsh', 'bash'];
|
package/cordis.patch.yml
CHANGED
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
## Root agent rules
|
|
43
43
|
|
|
44
44
|
1. Plan first. Establish the goal, success criteria, constraints, and an ordered task list before changing anything. Revise the plan when evidence contradicts it.
|
|
45
|
-
2. Delegate research by default. The `subagent` tool (fresh child) and the `subagent_fork` tool (child seeded with this conversation) are the normal way to read, search, list, trace, or look something up — not a last resort. Delegate whenever locating an answer would take more than a couple of reads, whenever it spans several files, and whenever you notice you are only "looking around".
|
|
45
|
+
2. Delegate research by default. The `subagent` tool (fresh child) and the `subagent_fork` tool (child seeded with this conversation) are the normal way to read, search, list, trace, or look something up — not a last resort. Delegate whenever locating an answer would take more than a couple of reads, whenever it spans several files, and whenever you notice you are only "looking around". A subagent does not have `subagent`; it reads for itself, or uses `subagent_fork` if the tree still allows one more level.
|
|
46
46
|
3. Keep delegations independent and concurrent. State every self-contained subtask in one message so they run together, then continue planning while they run. Wait only when your next action needs a specific result.
|
|
47
47
|
4. Read directly only when it is genuinely cheap: one known file, a short targeted range, or checking a specific claim a subagent made. Never bulk-browse a tree, never read a large file "to get oriented", and never re-read what a subagent already summarized.
|
|
48
48
|
5. Ask for the summary shape you need. Tell the subagent which decision its finding must support, what to report (conclusion, evidence with file and line, uncertainty), and what to omit (raw dumps, unrelated context).
|
|
@@ -179,7 +179,6 @@
|
|
|
179
179
|
- list_agents
|
|
180
180
|
- send_message
|
|
181
181
|
- interrupt_agent
|
|
182
|
-
- subagent
|
|
183
182
|
- subagent_fork
|
|
184
183
|
- id: tool-subagent-fork
|
|
185
184
|
name: '@deepseek-ai/dsh-tool-subagent'
|
|
@@ -207,7 +206,6 @@
|
|
|
207
206
|
- list_agents
|
|
208
207
|
- send_message
|
|
209
208
|
- interrupt_agent
|
|
210
|
-
- subagent
|
|
211
209
|
- subagent_fork
|
|
212
210
|
- id: tool-subagent-codex
|
|
213
211
|
name: '@deepseek-ai/dsh-tool-subagent'
|
package/docs/DEVELOPMENT.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# root-planner preset bundle — maintainer notes
|
|
2
2
|
|
|
3
|
-
[中文说明](../README.md) | [
|
|
3
|
+
[English README](../README.md) | [中文说明](../README.zh.md) | [Install](../INSTALL.md)
|
|
4
4
|
|
|
5
5
|
An agent preset for DSH Web that splits **reading** from **planning and mutating**:
|
|
6
6
|
the root agent keeps its context for analysis, planning, and decisions, delegates
|
|
@@ -29,6 +29,10 @@ Paths below are relative to the repository root; this file lives in `docs/`.
|
|
|
29
29
|
| `verify-depth-override.mjs` | Checks that the profile patch layer still parses and that the `subagent` depth override landed in the composed profile. |
|
|
30
30
|
| `package.json` | Bundle manifest (`dsh.bundle.patch`). |
|
|
31
31
|
| `.gitattributes` | Pins LF; without it a Windows checkout breaks the byte-identity check. |
|
|
32
|
+
| `README.md` / `README.zh.md` | The English and Chinese front pages, cross-linked. npm renders the repository root `README.md`, which is why the English one holds that name. |
|
|
33
|
+
| `INSTALL.md` | Recipient-facing setup steps and troubleshooting (Chinese). |
|
|
34
|
+
| `.github/CONTRIBUTING.md` | The contributor workflow: regenerate, verify, and the rules a change must not break. |
|
|
35
|
+
| `.github/workflows/verify.yml` | CI: fails when `cordis.patch.yml` drifts from `persona.txt`, and runs the mask check. |
|
|
32
36
|
|
|
33
37
|
```sh
|
|
34
38
|
node build-patch.mjs # after every persona.txt edit
|
|
@@ -54,9 +58,30 @@ targets:
|
|
|
54
58
|
|---|---|---|
|
|
55
59
|
| Absolute path | `C:\path\to\root-planner-preset` | Works from a checkout, a share, or a USB drive. Already-installed bundles must be addressed by package name, not path. |
|
|
56
60
|
| Git repository | `github:you/root-planner-preset` | Also `https://…`, `git+…`, `gitlab:`/`gist:` forms. The manager pre-checks GitHub reachability with `git ls-remote`. |
|
|
57
|
-
| npm registry | `dsh-root-planner-preset`
|
|
61
|
+
| npm registry | `dsh-root-planner-preset` (`@1.0.0` to pin) | **Published.** Preferred: a prebuilt install skips pnpm's build-approval step. |
|
|
58
62
|
| Tarball URL | `https://…/dsh-root-planner-preset-1.0.0.tgz` | For a file server or release asset. |
|
|
59
63
|
|
|
64
|
+
### Publishing to npm
|
|
65
|
+
|
|
66
|
+
The package is live at <https://www.npmjs.com/package/dsh-root-planner-preset>.
|
|
67
|
+
Two things about this machine and account cost real time to discover; both are
|
|
68
|
+
permanent conditions, not accidents:
|
|
69
|
+
|
|
70
|
+
- **The configured registry is `registry.npmmirror.com`**, a read-only mirror.
|
|
71
|
+
Every publish must name the official registry explicitly:
|
|
72
|
+
`npm publish --registry https://registry.npmjs.org/ --access public`.
|
|
73
|
+
- **The publishing account uses npm's staged publishing.** A `npm publish` can
|
|
74
|
+
report success and still leave the version off the registry, because it went to
|
|
75
|
+
the stage instead; `npm stage list` may show nothing while the registry refuses
|
|
76
|
+
that version with `E409 Cannot publish over previously staged version`. The
|
|
77
|
+
reliable check is never the command's exit code — it is
|
|
78
|
+
`https://registry.npmjs.org/<name>/<version>` returning 200. Version `1.0.0`
|
|
79
|
+
reached the registry only after approval on npmjs.com, not from the CLI.
|
|
80
|
+
|
|
81
|
+
The `0.0.0-stage` version on the registry is an automatic placeholder from that
|
|
82
|
+
workflow. It is harmless but pointless; deprecate it rather than treat it as a
|
|
83
|
+
release. Never publish a real version below it.
|
|
84
|
+
|
|
60
85
|
### What the recipient's environment must provide
|
|
61
86
|
|
|
62
87
|
1. **A DSH version whose presets carry the same tool row ids.** The patch mirrors
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-root-planner-preset",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "DSH agent preset
|
|
5
|
+
"description": "DSH agent preset that outsources file reading to strictly read-only subagents, while the root agent keeps the plan and owns every file mutation.",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "XiaoOu2024",
|
|
8
8
|
"homepage": "https://github.com/XiaoOu2024/dsh-root-planner-preset#readme",
|
|
@@ -26,5 +26,8 @@
|
|
|
26
26
|
"bundle": {
|
|
27
27
|
"patch": "./cordis.patch.yml"
|
|
28
28
|
}
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"js-yaml": "^4.1.0"
|
|
29
32
|
}
|
|
30
33
|
}
|
package/persona.txt
CHANGED
|
@@ -21,7 +21,7 @@ Reading is not planning. Raw file contents and raw query results belong in the a
|
|
|
21
21
|
## Root agent rules
|
|
22
22
|
|
|
23
23
|
1. Plan first. Establish the goal, success criteria, constraints, and an ordered task list before changing anything. Revise the plan when evidence contradicts it.
|
|
24
|
-
2. Delegate research by default. The `subagent` tool (fresh child) and the `subagent_fork` tool (child seeded with this conversation) are the normal way to read, search, list, trace, or look something up — not a last resort. Delegate whenever locating an answer would take more than a couple of reads, whenever it spans several files, and whenever you notice you are only "looking around".
|
|
24
|
+
2. Delegate research by default. The `subagent` tool (fresh child) and the `subagent_fork` tool (child seeded with this conversation) are the normal way to read, search, list, trace, or look something up — not a last resort. Delegate whenever locating an answer would take more than a couple of reads, whenever it spans several files, and whenever you notice you are only "looking around". A subagent does not have `subagent`; it reads for itself, or uses `subagent_fork` if the tree still allows one more level.
|
|
25
25
|
3. Keep delegations independent and concurrent. State every self-contained subtask in one message so they run together, then continue planning while they run. Wait only when your next action needs a specific result.
|
|
26
26
|
4. Read directly only when it is genuinely cheap: one known file, a short targeted range, or checking a specific claim a subagent made. Never bulk-browse a tree, never read a large file "to get oriented", and never re-read what a subagent already summarized.
|
|
27
27
|
5. Ask for the summary shape you need. Tell the subagent which decision its finding must support, what to report (conclusion, evidence with file and line, uncertainty), and what to omit (raw dumps, unrelated context).
|
package/verify-patch.mjs
CHANGED
|
@@ -2,13 +2,15 @@
|
|
|
2
2
|
// 1. every patch file parses as YAML,
|
|
3
3
|
// 2. the preset row and its delegation rows exist,
|
|
4
4
|
// 3. the parsed persona is byte-identical to persona.txt (no YAML mangling),
|
|
5
|
-
// 4. the child tool mask
|
|
5
|
+
// 4. the child tool mask names only global tools, excludes every mutation entry
|
|
6
|
+
// point, and excludes agent-scoped names that tools.restrict() would reject.
|
|
6
7
|
//
|
|
7
8
|
// Usage: node verify-patch.mjs
|
|
8
9
|
|
|
9
10
|
import { readFileSync } from 'node:fs';
|
|
10
11
|
import { dirname, join } from 'node:path';
|
|
11
12
|
import { fileURLToPath } from 'node:url';
|
|
13
|
+
import { CHILD_TOOLS, MUTATION_TOOLS, SCOPE_LOCAL_TOOLS } from './child-tools.mjs';
|
|
12
14
|
import { loadYaml, loaderSchema as makeLoaderSchema } from './lib-yaml.mjs';
|
|
13
15
|
|
|
14
16
|
const yaml = loadYaml();
|
|
@@ -69,55 +71,80 @@ const childTools = [
|
|
|
69
71
|
return { id, toolName, allow: row?.config?.toolFilter?.allow };
|
|
70
72
|
});
|
|
71
73
|
|
|
72
|
-
// The mask must deny every mutation entry point
|
|
73
|
-
const mutations = ['write', 'edit', 'ops_replace', 'ops_bash', 'pwsh', 'bash', 'ops_run_background'];
|
|
74
|
+
// The mask must deny every mutation entry point, and it can only name global tools.
|
|
74
75
|
for (const { id, allow } of childTools) {
|
|
75
76
|
check(Array.isArray(allow) && allow.length > 0, `${id} declares a non-empty allow list`);
|
|
76
|
-
for (const denied of
|
|
77
|
+
for (const denied of MUTATION_TOOLS) {
|
|
77
78
|
check(!allow?.includes(denied), `${id} mask excludes ${denied}`);
|
|
78
79
|
}
|
|
79
80
|
check(allow?.includes('read'), `${id} mask keeps read`);
|
|
80
|
-
|
|
81
|
+
for (const scoped of SCOPE_LOCAL_TOOLS) {
|
|
82
|
+
check(
|
|
83
|
+
!allow?.includes(scoped),
|
|
84
|
+
`${id} mask excludes the agent-scoped tool "${scoped}" (tools.restrict() would refuse it)`,
|
|
85
|
+
);
|
|
86
|
+
}
|
|
81
87
|
}
|
|
82
88
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
89
|
+
/**
|
|
90
|
+
* The global tool inventory of the author's profile, captured from a runtime
|
|
91
|
+
* error rather than hand-kept: `tools.restrict()` prints every known global tool
|
|
92
|
+
* when it refuses a name. Replace this list with the equivalent line from your
|
|
93
|
+
* own environment — it is what makes the check below meaningful.
|
|
94
|
+
*/
|
|
95
|
+
const OBSERVED_GLOBAL_TOOLS = new Set([
|
|
96
|
+
'ask_user_question',
|
|
97
|
+
'create_goal',
|
|
98
|
+
'edit',
|
|
99
|
+
'exit_plan_mode',
|
|
100
|
+
'get_goal',
|
|
87
101
|
'glob',
|
|
88
102
|
'grep',
|
|
89
|
-
'
|
|
103
|
+
'interrupt_agent',
|
|
104
|
+
'job_kill',
|
|
105
|
+
'job_list',
|
|
106
|
+
'job_output',
|
|
107
|
+
'list_agents',
|
|
90
108
|
'ops_glob',
|
|
91
109
|
'ops_grep',
|
|
110
|
+
'ops_inspect_local_file',
|
|
92
111
|
'ops_replace',
|
|
93
|
-
'
|
|
94
|
-
'
|
|
112
|
+
'present',
|
|
113
|
+
'pwsh',
|
|
114
|
+
'read',
|
|
95
115
|
'read_image',
|
|
116
|
+
'send_message',
|
|
96
117
|
'skill',
|
|
118
|
+
'subagent_fork',
|
|
97
119
|
'todo_write',
|
|
98
|
-
'ask_user_question',
|
|
99
|
-
'get_goal',
|
|
100
|
-
'create_goal',
|
|
101
120
|
'update_goal',
|
|
102
|
-
'
|
|
103
|
-
'
|
|
104
|
-
'interrupt_agent',
|
|
105
|
-
'subagent',
|
|
106
|
-
'subagent_fork',
|
|
107
|
-
'present',
|
|
108
|
-
'write',
|
|
109
|
-
'edit',
|
|
110
|
-
'pwsh',
|
|
111
|
-
'ops_bash',
|
|
112
|
-
'job_output',
|
|
113
|
-
'job_list',
|
|
114
|
-
'job_kill',
|
|
121
|
+
'web_fetch',
|
|
122
|
+
'web_search',
|
|
115
123
|
'workflow',
|
|
116
|
-
'
|
|
124
|
+
'write',
|
|
117
125
|
]);
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Tools that are agent-scoped rather than global, so the mask can never name
|
|
129
|
+
* them. `subagent` is the one this bundle ran into: with
|
|
130
|
+
* `modelSelectionSettings: true` it is mounted inside each composed Agent's own
|
|
131
|
+
* scope, and `tools.restrict()` accepts global names only.
|
|
132
|
+
*/
|
|
118
133
|
for (const { id, allow } of childTools) {
|
|
119
|
-
const unknown = (allow ?? []).filter((name) => !
|
|
120
|
-
check(
|
|
134
|
+
const unknown = (allow ?? []).filter((name) => !OBSERVED_GLOBAL_TOOLS.has(name));
|
|
135
|
+
check(
|
|
136
|
+
unknown.length === 0,
|
|
137
|
+
`${id} mask names only tools the runtime reports as global` +
|
|
138
|
+
(unknown.length ? ` (not global: ${unknown.join(', ')})` : ''),
|
|
139
|
+
);
|
|
140
|
+
const missing = CHILD_TOOLS.filter((name) => !(allow ?? []).includes(name));
|
|
141
|
+
const extra = (allow ?? []).filter((name) => !CHILD_TOOLS.includes(name));
|
|
142
|
+
check(
|
|
143
|
+
missing.length === 0 && extra.length === 0,
|
|
144
|
+
`${id} mask matches child-tools.mjs (${CHILD_TOOLS.length} entries)` +
|
|
145
|
+
(missing.length ? ` — missing: ${missing.join(', ')}` : '') +
|
|
146
|
+
(extra.length ? ` — unexpected: ${extra.join(', ')}` : ''),
|
|
147
|
+
);
|
|
121
148
|
}
|
|
122
149
|
|
|
123
150
|
process.stdout.write(failures.length === 0 ? '\nALL CHECKS PASSED\n' : `\n${failures.length} CHECK(S) FAILED\n`);
|