dsh-paoding 0.2.0

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.
Files changed (42) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +95 -0
  3. package/README_en.md +95 -0
  4. package/bin/dsh-paoding.mjs +12 -0
  5. package/cordis.patch.yml +6 -0
  6. package/docs/architecture.md +292 -0
  7. package/docs/architecture_en.md +363 -0
  8. package/docs/configuration.md +551 -0
  9. package/docs/configuration_en.md +560 -0
  10. package/docs/images/1.png +0 -0
  11. package/docs/images/2.png +0 -0
  12. package/docs/images/3.png +0 -0
  13. package/docs/images/4.png +0 -0
  14. package/docs/images/5.png +0 -0
  15. package/docs/images/6.png +0 -0
  16. package/docs/installation.md +364 -0
  17. package/docs/installation_en.md +367 -0
  18. package/docs/orchestration.md +254 -0
  19. package/docs/orchestration_en.md +255 -0
  20. package/install.sh +31 -0
  21. package/package.json +29 -0
  22. package/plugins/paoding-config-ui/api-core.mjs +100 -0
  23. package/plugins/paoding-config-ui/index.mjs +327 -0
  24. package/plugins/paoding-config-ui/lib/base.css +615 -0
  25. package/plugins/paoding-config-ui/lib/client.js +1154 -0
  26. package/plugins/paoding-config-ui/package.json +23 -0
  27. package/plugins/paoding-config-ui/runtime-inventory.mjs +212 -0
  28. package/presets/orchestrator/agent.cordis.yml +323 -0
  29. package/presets/orchestrator/preset.yml +6 -0
  30. package/presets/orchestrator/restrict.mjs +191 -0
  31. package/tools/install.mjs +24 -0
  32. package/tools/lib/alloc.mjs +138 -0
  33. package/tools/lib/cli.mjs +522 -0
  34. package/tools/lib/compose.mjs +647 -0
  35. package/tools/lib/config.mjs +297 -0
  36. package/tools/lib/host.mjs +288 -0
  37. package/tools/lib/skills.mjs +146 -0
  38. package/tools/lib/spans.mjs +188 -0
  39. package/tools/lib/state.mjs +328 -0
  40. package/tools/lib/util.mjs +26 -0
  41. package/tools/lib/wizard.mjs +516 -0
  42. package/tools/lib/yaml.mjs +266 -0
@@ -0,0 +1,363 @@
1
+ <!-- docs/architecture_en.md — dsh-paoding (Pao Ding) architecture, English version -->
2
+
3
+ ← [dsh-paoding](../README_en.md) | [Installation](installation_en.md) | **Architecture** | [Orchestration](orchestration_en.md) | [Configuration](configuration_en.md)
4
+
5
+ [中文](architecture.md) · English
6
+
7
+ # Architecture
8
+
9
+ > Reader: technical users and contributors who want to understand why it is designed this way, the
10
+ > costs and benefits, and the mechanisms underneath. Facts follow the repository source
11
+ > (`presets/orchestrator/agent.cordis.yml`, `presets/orchestrator/restrict.mjs`,
12
+ > `presets/orchestrator/preset.yml`); where they conflict with the README, the source wins.
13
+
14
+ ## Overview
15
+
16
+ dsh-paoding (Chinese brand name 庖丁 / Pao Ding, from the Zhuangzi parable "庖丁解牛" — the butcher whose
17
+ knife never dulls because he cuts along the natural seams) is an **orchestrator-preset generator and
18
+ configurator** for DSH (DeepSeek Harness, an agent runtime). It reshapes DSH's default shape — a monolithic
19
+ agent that loads every tool on every request (~60 tools, ~16.2k tokens of tool tax per request) — into an
20
+ architecture of **one orchestrating main agent plus role-specialized sub-agents that load tools on demand**:
21
+
22
+ - **The hot path stays on the main agent**: internal file search (glob / grep / read / read_image / bash)
23
+ is reached directly with zero delegation round-trip;
24
+ - **Cold paths are delegated to role sub-agents**: external web research (search_external), design (design),
25
+ code implementation (implement), and whole-repository deep search (search_internal_deep) are each handed
26
+ to a sub-agent carrying only the tools it needs — **pay per use, once used once paid**;
27
+ - **Context isolation**: every sub-agent keeps a small, specialized context, and the main agent integrates
28
+ only summaries.
29
+
30
+ The whole solution is "pure configuration plus one waterfall-filter plugin whose core logic is roughly 130
31
+ lines (`restrict.mjs`, ~190 lines with comments)" — **zero changes to DSH source code**, hot-swappable
32
+ (install / uninstall / modify are all plain file operations).
33
+
34
+ The essential differences from monolithic mode:
35
+
36
+ | Dimension | Monolithic mode (default) | Orchestrator mode (this preset) |
37
+ |---|---|---|
38
+ | Responsibility | One agent does everything | Main agent decomposes and integrates; role sub-agents each do one job |
39
+ | Cost | ~16.2k tokens tool tax paid on every request | Main agent ~5.5k; role surfaces ~3.0k–3.7k, paid only when delegated |
40
+ | Context | Every search dump, design draft and diff accumulates in the main context | Worker contexts are small and specialized and discarded after use; the main agent receives only summaries |
41
+
42
+ The static source of the composition is `presets/orchestrator/agent.cordis.yml`, layered on top of DSH's
43
+ builtin `standard` preset in full (see the "Under the hood" section below); at runtime
44
+ `restrict.mjs` trims the main agent's tool surface. The four role delegation instances are
45
+ `delegation-search-external` / `delegation-design` / `delegation-implement` /
46
+ `delegation-search-internal-deep`, all inside the composition's delegation group. That group also carries the
47
+ generic delegation tools `subagent` / `subagent_fork` / `workflow` / `ralph` (they are not in the main-agent
48
+ whitelist; role delegation is the preset's primary path).
49
+
50
+ The architecture diagram below shows the main-agent tool surface and the four role delegations (the complete
51
+ allow lists are in the "Roles & tool surfaces" section below):
52
+
53
+ ```
54
+ ┌─ main agent · orchestrator (orchestrator-mode preset, 21-tool whitelist)─────────────┐
55
+ │ delegate : search_external / design / implement / search_internal_deep │
56
+ │ manage : send_message / list_agents / interrupt_agent │
57
+ │ hot path : glob / grep / read / read_image / bash ◄── zero delegation round-trip │
58
+ │ coordinate: todo_write / ask_user_question / get_goal / create_goal / │
59
+ │ update_goal / exit_plan_mode / job_output / job_list / job_kill │
60
+ │ job : decompose → solve internal problems in place → delegate only │
61
+ │ research / design / implementation / whole-repo deep search │
62
+ │ → integrate summaries only │
63
+ └───────────┬───────────────────┬───────────────────┬───────────────────┬──────────────┘
64
+ │ │ │ │
65
+ ▼ ▼ ▼ ▼
66
+ search_external design implement search_internal_deep
67
+ (web research) (design) (implementation) (repo deep search)
68
+ (on demand) (on demand) (on demand) (on demand)
69
+ │ │ │ │
70
+ ▼ ▼ ▼ ▼
71
+ web_search read/read_image read/write/edit glob/grep/read
72
+ mcp__tavily__* glob/grep/bash glob/grep/bash read_image/bash
73
+ (5 tools) skill/write/edit skill/web_search ask_user_question
74
+ glob/grep/read ask_user_question ask_user_question todo_write
75
+ ask_user_question todo_write todo_write job_output
76
+ (no write/edit) job_output job_output job_list
77
+ job_list job_list/job_kill job_kill
78
+ job_kill get_goal (read-only; no
79
+ create_goal file writes,
80
+ update_goal no network)
81
+ ```
82
+
83
+ In the diagram:
84
+
85
+ - The **main agent** box lists its 21-entry whitelist, grouped into delegation (search_external / design /
86
+ implement / search_internal_deep), child management (send_message / list_agents / interrupt_agent), the
87
+ internal-search hot path (glob / grep / read / read_image / bash), and coordination (todo_write /
88
+ ask_user_question / get_goal / create_goal / update_goal / exit_plan_mode / job_output / job_list /
89
+ job_kill);
90
+ - Four arrows fan out to the **role sub-agents**, each created on demand (cold path). search_external
91
+ carries web_search, the five `mcp__tavily__*` tools, plus glob/grep/read and ask_user_question, with no
92
+ write/edit; design carries read/read_image/glob/grep/bash/skill/write/edit plus coordination tools;
93
+ implement carries read/write/edit/glob/grep/bash/skill/web_search plus coordination and goal tools;
94
+ search_internal_deep carries the full read-only internal-search surface (glob/grep/read/read_image/bash
95
+ plus ask_user_question and job tools) — no file modification, no network.
96
+
97
+ ## Why it costs less
98
+
99
+ The core benefit of this preset is **pay-per-use plus context isolation**; the tool tax is only the most
100
+ visible symptom. Per-request tool-tax estimates (source: the header comment of `agent.cordis.yml` and
101
+ measured calibration; figures are order-of-magnitude estimates):
102
+
103
+ | Setup | Tool surface | tokens/request | Notes |
104
+ |---|---|---|---|
105
+ | Monolithic (status quo, untrimmed) | All 60 tools | **~16.2k** | Paid in full on every request |
106
+ | Orchestrator main agent | 21-entry whitelist (∩ registry) | **~5.5k** | ↓66%; zero delegation latency for internal search |
107
+ | search_external sub-agent | 10 allowed entries | ~3.0k | Paid only when web research actually runs |
108
+ | design sub-agent | 13 allowed entries | ~3.2k | Paid only when a design task is delegated |
109
+ | implement sub-agent | 16 allowed entries | ~3.7k | Paid only when an implementation task is delegated |
110
+ | search_internal_deep sub-agent | 10 allowed entries | not separately estimated | Paid only when a deep repo search is delegated |
111
+
112
+ Note: role tool counts above follow the actual `allow` entries in `agent.cordis.yml` (design 13 / implement
113
+ 16); the old README figures of 14 / 17 and a 22-entry whitelist are outdated.
114
+
115
+ **The real win is context isolation, not the few thousand tokens saved per se**:
116
+
117
+ - A monolithic agent's context accumulates every search dump, every design draft and every diff — even when a
118
+ tool is unused in the current request, its past outputs still occupy the window;
119
+ - In the orchestrator architecture every worker keeps a small, specialized context (its own role tools and
120
+ outputs only); roles are one-shot by default and discarded after use. The main agent receives only
121
+ structured summaries and integrates them — it never pours a sub-agent's full context into its own window;
122
+ - Consequently the main agent's window size is decoupled from "how much work this round delegated"; in long
123
+ sessions the token level is dominated by the orchestrator's own summaries and decisions (combined with
124
+ compaction governance, see the "Performance: token governance" section below).
125
+
126
+ **Skill-catalog injection into the main agent is zero** (measured ≈1.3k tokens/round for a 4455-byte
127
+ catalog of 10 skills; eliminated by a registry-level deny — mechanism ⑤ in the "Under the hood" section).
128
+ The main agent carries no `skill` tool yet must still be able to use skills on demand, through two channels:
129
+
130
+ - `main_agent_skills` (soft guidance, the default channel): each skill occupies a single persona line
131
+ (skill name + description + SKILL.md path) — zero catalog tax; when a task matches a skill the main agent
132
+ reads the full SKILL.md on demand with its own `read` tool and follows it. Unused skills cost nothing;
133
+ - `main_agent_skills_inline` (optional hard inlining): the full SKILL.md body of the listed skills is
134
+ inlined into the persona at a fixed per-round cost — for style skills such as caveman that must apply
135
+ unconditionally.
136
+
137
+ Configuration details for both channels: [docs/configuration_en.md](configuration_en.md).
138
+
139
+ ## Roles & tool surfaces
140
+
141
+ ### Main-agent tool surface (21-entry whitelist)
142
+
143
+ The hardcoded `MAIN_AGENT_ALLOW` in `restrict.mjs` has **21 entries** in four groups (order follows the
144
+ source comments):
145
+
146
+ | Group | Tools |
147
+ |---|---|
148
+ | Role delegation (4; the default full set, three can be deleted via config) | `search_external` `design` `implement` `search_internal_deep` |
149
+ | Child management (3) | `send_message` `list_agents` `interrupt_agent` |
150
+ | Internal search · hot path (5) | `glob` `grep` `read` `read_image` `bash` |
151
+ | Coordination (9) | `todo_write` `ask_user_question` `get_goal` `create_goal` `update_goal` `exit_plan_mode` `job_output` `job_list` `job_kill` |
152
+
153
+ **Host tools are not hardcoded in the whitelist constant**: `mcp__*` and plugin tools (codegraph /
154
+ memory_search / mnemon_* etc.) are "install-time facts" — when the main agent needs one, it is ticked in
155
+ `main_agent_extra` in the configuration file `$DSH_HOME/dsh-paoding.config.yml`, the generator injects the
156
+ still-enabled ones into the restrict `config.allow` based on runtime detection, and disabled ones are
157
+ dropped automatically (see [docs/configuration_en.md](configuration_en.md)). The main agent's default surface
158
+ therefore excludes `web_search`, every `mcp__tavily__*`, `write` / `edit` / `str_replace_editor`, `skill`,
159
+ `workflow`, `ralph`, and the unfiltered `subagent` / `subagent_fork` — either because they belong to role
160
+ sub-agents or because their cost is deliberately zeroed out.
161
+
162
+ ### Built-in role agents at a glance
163
+
164
+ Each role is a `dsh-tool-subagent` instance: which delegation tool the main agent calls selects that
165
+ sub-agent's tool surface (the sub-agent is created with a `tools.restrict()` applied from its `toolFilter`,
166
+ see mechanism ④ in the "Under the hood" section below). The role personas embedded in the main-agent
167
+ persona and the delegation-failure SOP live in [docs/orchestration_en.md](orchestration_en.md).
168
+
169
+ The four built-in roles are just the factory division of labor: through the same mechanism, any combination
170
+ of tools and skills becomes a new delegation tool — write a toolName under the config `roles` key (or create
171
+ it in the wizard / config UI), the installer generates a `delegation-<toolName>` block, injects the
172
+ toolName into the main-agent allow list, and appends the role's persona first line (its duty sentence)
173
+ to the main-agent persona as a delegation row (a role with no tools is rejected at apply time); see
174
+ section 6 of [docs/configuration_en.md](configuration_en.md).
175
+ Custom roles support the same `model` / `provider` dedicated-model sub-keys as the built-in roles (same
176
+ sub-keys, same generation mechanism — see 2.5).
177
+ For example, a `ppt` agent carrying the `html-ppt` skill gives slide-making its own dedicated sub-agent.
178
+
179
+ | Delegation tool | Instance ID | Persona in one sentence | Load cost |
180
+ |---|---|---|---|
181
+ | `search_external` | `delegation-search-external` | Web research only (web_search + tavily), never modifies files; returns a structured research summary (findings, sources, open questions) | ~3.0k, only when delegated |
182
+ | `design` | `delegation-design` | Produces UI/UX designs, wireframes and frontend specs; may load design skills (frontend-design, html-ppt) via the skill tool; reads reference material first, delivers a spec, does not implement final code | ~3.2k, only when delegated |
183
+ | `implement` | `delegation-implement` | Writes and edits code: read surrounding context → surgical change → verify (build/test/grep); does not redesign architecture | ~3.7k, only when delegated |
184
+ | `search_internal_deep` | `delegation-search-internal-deep` | Repository exploration only: never modifies files, never uses the network; handles tasks too heavy for the main agent's context (whole-repo grep dumps, full-file reads beyond a page, cross-directory symbol/definition tracking); returns condensed summaries only | not separately estimated, only when delegated |
185
+
186
+ The table above is the factory default; the configuration layer can trim and customize it (see
187
+ [docs/configuration_en.md](configuration_en.md)): the trio `search_external` / `design` / `implement` can be
188
+ deleted wholesale via `roles_remove` (2.3; `search_internal_deep` cannot); a built-in role can carry a display
189
+ name `roles.<toolName>.name`, which rewrites only the subject of its persona identity sentence (2.4); it can
190
+ also be pinned to a dedicated model `roles.<toolName>.model` / `.provider`, injected by the generator as
191
+ `agentOptions` in the delegation block, defaulting to following the main agent (2.5); and the main agent itself
192
+ can be renamed via `main_agent_name` — one key, two places (section 5).
193
+
194
+ ### Per-role toolFilter.allow detail
195
+
196
+ The allow lists below are transcribed verbatim from `agent.cordis.yml` (at install time the generator
197
+ rewrites each list as "config/static intent ∩ runtime-detected host tools", see
198
+ [docs/configuration_en.md](configuration_en.md)).
199
+
200
+ **`search_external` (external research, 10 entries)**
201
+
202
+ | Field | Value |
203
+ |---|---|
204
+ | Persona job | Web research only, returning a structured research summary (findings, sources, open questions); never modifies files |
205
+ | toolFilter.allow | `web_search`, `mcp__tavily__tavily_search`, `mcp__tavily__tavily_crawl`, `mcp__tavily__tavily_extract`, `mcp__tavily__tavily_map`, `mcp__tavily__tavily_research`, `glob`, `grep`, `read`, `ask_user_question` |
206
+ | Load cost | ~3.0k tokens, incurred only when web research actually runs |
207
+
208
+ **`design` (design, 13 entries)**
209
+
210
+ | Field | Value |
211
+ |---|---|
212
+ | Persona job | Produces UI/UX designs, wireframes and frontend specs; loads design skills (frontend-design, html-ppt) via the skill tool; reads reference material first, then delivers a spec; does not implement final code |
213
+ | toolFilter.allow | `read`, `read_image`, `glob`, `grep`, `bash`, `skill`, `write`, `edit`, `ask_user_question`, `todo_write`, `job_output`, `job_list`, `job_kill` |
214
+ | Load cost | ~3.2k tokens, incurred only when a design task is delegated |
215
+
216
+ **`implement` (implementation, 16 entries)**
217
+
218
+ | Field | Value |
219
+ |---|---|
220
+ | Persona job | Writes and edits code: read surrounding context first → surgical changes → verify (build/test/grep), reporting what changed and how it was verified; does not redesign architecture |
221
+ | toolFilter.allow | `read`, `write`, `edit`, `glob`, `grep`, `bash`, `skill`, `web_search`, `ask_user_question`, `todo_write`, `job_output`, `job_list`, `job_kill`, `get_goal`, `create_goal`, `update_goal` |
222
+ | Load cost | ~3.7k tokens, incurred only when an implementation task is delegated |
223
+
224
+ **`search_internal_deep` (whole-repo deep search, 10 entries)**
225
+
226
+ | Field | Value |
227
+ |---|---|
228
+ | Persona job | Repository exploration only: never modifies files, never uses the network; handles whole-repo grep dumps, full-file reads beyond a page, and cross-directory symbol/definition tracking; searches thoroughly, then returns a CONCISE structured summary (what was searched, key files with line numbers, definitions/symbols found, and the takeaway for the next decision) — never echoes large dumps back |
229
+ | toolFilter.allow | `glob`, `grep`, `read`, `read_image`, `bash`, `ask_user_question`, `todo_write`, `job_output`, `job_list`, `job_kill` |
230
+ | Load cost | not separately estimated, incurred only when a deep repo search is delegated |
231
+
232
+ > Note: `codegraph` and `memory_search` were removed from this role on 2026-09-02 — when their plugins
233
+ > (codegraph MCP / magic-memory) are disabled they are outside the sub-agent's visible registry, and
234
+ > `tools.restrict()` would refuse to create the role with unknown tools. Re-add them on demand after
235
+ > re-enabling the plugins (allow lists use exact name matching, no globs — see the "Known limitations"
236
+ > section below).
237
+
238
+ ## Under the hood
239
+
240
+ This preset invents no new mechanism — every layer maps onto a native DSH capability, and the preset merely
241
+ composes them. Each item below is stated as a design decision (the first release failed at runtime because
242
+ ① was missing; that lesson is now a hard constraint of the composition).
243
+
244
+ **① Basic tools are registered at the preset layer; the orchestrator must compose the full standard
245
+ preset.** In DSH, base tools such as read/write/edit/glob/grep/bash/skill/web_search are **not provided by
246
+ the global layer** — they are mounted by the composition of the builtin `standard` preset. The orchestrator
247
+ composition must therefore include every row of `standard` verbatim — tool-bash / tool-pwsh / fs / fs-search
248
+ / jobs / skill / goal / planning / compaction / delegation / ask-user / todo / web — otherwise a sub-agent's
249
+ `tools.restrict()` fails with `unknown global tools`. This is a hard design constraint: a sub-agent inherits
250
+ the tools **registered by the composition itself**, and a missing row means the tool is absent from the
251
+ sub-agent's registry. The host layer (e.g. `~/.dsh/cordis.patch.yml`) mounts only MCP servers and local
252
+ plugin tools.
253
+
254
+ **② The main-agent surface trim is a waterfall filter on the model-facing catalog.** `restrict.mjs` hooks
255
+ the `system-prompt/assemble` waterfall (`prepend: true` places the filter outermost, so `await next()`
256
+ observes the fully assembled downstream result before the allow list is applied to `assembly.tools`).
257
+ Design points:
258
+
259
+ - **Fail-open**: any unexpected error inside the filter returns the assembly unchanged, so a filter bug can
260
+ never brick every request of a session;
261
+ - **An empty allow list refuses to load**: `apply()` throws a `TypeError` when `config.allow` is empty —
262
+ preventing the main agent from being stripped of every tool (the install-time generator also rejects a
263
+ removal that results in an empty list).
264
+
265
+ **③ Main vs. child is distinguished by the session header.** A child session's header is stamped
266
+ `origin: 'subagent'` (`delegationDepth >= 1` as a fallback; see dsh-subagent's childSessionMeta). The
267
+ waterfall lets children through untouched — a role's surface is already constrained by its own `toolFilter`,
268
+ and re-filtering children here would intersect with (and wrongly shrink) those role surfaces.
269
+
270
+ **④ A role sub-agent's surface is a per-child `tools.restrict()`.** Each role is a `dsh-tool-subagent`
271
+ instance declaring a `toolName` (the tool name the main agent calls) and a `toolFilter.allow`. At child
272
+ creation the child context runs `childCtx.tools.restrict(toolFilter)`: every allow name must exist in the
273
+ sub-agent's visible registry (= this composition in full + host-layer MCP/plugins), otherwise creation is
274
+ rejected with unknown tools. Hence "which delegation tool the main agent calls selects which tool surface";
275
+ tools not explicitly allowed are entirely invisible to the child.
276
+
277
+ **⑤ Zeroing the main agent's skill catalog needs two layers.** Removing `skill` from the model-facing
278
+ surface alone is insufficient: the `dsh-tool-skill` catalog-injection guard checks **registry
279
+ visibility** (`ctx.tools.get('skill', agent)`), not the model-facing list, so a prompt-level filter cannot
280
+ keep the catalog out (measured: a 4455-byte / 10-skill catalog ≈1.3k tokens/round). Therefore:
281
+
282
+ - the `system-prompt/assemble` waterfall filters `skill` out of the main agent's model-facing catalog
283
+ (layer 1);
284
+ - `MAIN_AGENT_REGISTRY_DENY = ['skill']` applies a registry-level deny to the main agent at `agent/created`
285
+ (layer 2); the catalog injection then stops and its cost drops to zero;
286
+ - children are exempt: their surface comes from each delegation's `toolFilter`, and per-agent restrictions
287
+ never cross scope layers (a child scope is a sibling under the shared standing mount, not a descendant of
288
+ the main agent's scope);
289
+ - design/implement and other sub-agents keep `skill` — DSH has no per-agent catalog filtering; catalogs are
290
+ collected by cwd and fully visible to sub-agents, guided by persona text. Explicitly whitelisting `skill`
291
+ in the main agent's allow list opts it back into the catalog (the registry deny is skipped accordingly).
292
+
293
+ **⑥ Role personas are the `persona` field of `dsh-tool-subagent`.** Each delegation instance carries its
294
+ own persona (the child agent's deployment:persona section), including the role boundary and a failure
295
+ reporting protocol; the main-agent persona additionally carries the delegation-failure SOP (branching on
296
+ stop reason: `error` → re-delegate once; `max-tokens` → split the task; `refusal` → do not retry; `aborted`
297
+ → retry only if the cancellation was accidental; poor output → re-delegate with concrete gaps; after two
298
+ failures of the same task, stop and report to the user; every re-delegation must carry the previous
299
+ integration summary) — see [docs/orchestration_en.md](orchestration_en.md).
300
+
301
+ **⑦ Hot-swap means presets are static directories.** Install, uninstall and modification are plain file
302
+ operations: the generator copies/rewrites the static sources into `$DSH_HOME/.agent-presets/orchestrator`;
303
+ editing the YAML / mjs, re-running the generator, and restarting the host or opening a new session takes
304
+ effect. Uninstalling means deleting the directory. No DSH source changes; the preset leaves nothing behind
305
+ when removed.
306
+
307
+ ## Performance: token governance
308
+
309
+ This section presents long-session token governance as a benchmark-style write-up: symptom (measurement) →
310
+ root cause → landed configuration → tunable parameters.
311
+
312
+ **Symptom and baseline (calibrated 2026-08-20).** In the same working directory with equivalent tasks, DSH's
313
+ average tokens per request in long sessions was ~2× that of a baseline implementation. After ruling out
314
+ output-truncation accounting differences (read defaults of 2000 lines plus a 50KB byte cap, bash tail+spill
315
+ to disk, grep inline caps — all same design as the baseline), the root cause was pinned down: **compaction
316
+ never fired with the default settings**. `dsh-compaction-basic` defaults to `thresholdRatio: 0.8` — inside
317
+ a 256K-token window compaction would only trigger at ≈209,715 tokens — so per-request tokens in long
318
+ sessions grew to a ~137K–142K plateau that never fell back (the level stayed below the trigger line).
319
+ Sessions on the baseline implementation for the same project had all undergone periodic compaction, with
320
+ history periodically compressed back down.
321
+
322
+ **Landed configuration.** The compaction-basic row in `agent.cordis.yml` now sets
323
+ `thresholdRatio: 0.45` (triggering at ≈117,965 tokens), with retainRatio keeping the default ~16% tail
324
+ (≈42K) plus a summary checkpoint; the steady-state average falls to the ~80K range. Each compaction run
325
+ performs one LLM summarization call that reuses the prefix cache, so its cost is negligible — triggering
326
+ somewhat more often costs almost nothing extra; 0.45 is the balance point between saving tokens and not
327
+ summarizing too frequently.
328
+
329
+ **Parameters and effect.**
330
+
331
+ | thresholdRatio | Trigger line (≈) | Steady-state average (≈) | Notes |
332
+ |---|---|---|---|
333
+ | 0.8 (DSH default) | 209,715 tokens (256K window) | 137K–142K, never falls back | Long sessions rarely compact |
334
+ | 0.45 (landed in this preset) | 117,965 tokens | ~80K | ~16% tail ≈42K + summary checkpoint |
335
+ | 0.35 (optional, more aggressive) | ~92K tokens | ~67K | Saves more tokens, summarizes more often |
336
+
337
+ After changing the threshold, re-run `./install.sh --auto` to apply. Companion components in the same
338
+ composition:
339
+
340
+ - `tool-result-pruner`: oversized tool results are trimmed with `thresholdChars: 8192 / headChars: 4096 /
341
+ tailChars: 1024` (keep head and tail), preventing a single giant output from inflating the window;
342
+ - `command-compact`: manual `/compact` for immediate compression at any point in a long session;
343
+ - the `search_internal_deep` role: whole-repo dumps and other large outputs are isolated out of the main
344
+ context — the hot path (single-file reads, precise greps) still runs on the main agent with zero
345
+ delegation latency; only whole-repo dumps / full-file reads / cross-directory symbol tracking are
346
+ delegated, and the child digests large outputs internally, returning condensed summaries only (see the
347
+ "Roles & tool surfaces" section above).
348
+
349
+ ## Known limitations
350
+
351
+ - **Role tool surfaces are statically bound** (decided by configuration / `toolFilter.allow`), not chosen
352
+ arbitrarily by the model at runtime. "Picking a role tool picks a tool surface" is an advantage for fixed
353
+ division of labor, but a tool cannot be granted to a role mid-conversation — changing it requires editing
354
+ the configuration / static source and re-running the generator.
355
+ - **`toolFilter.allow` uses exact name matching and supports no globs**: the five tavily tools must be
356
+ listed in full (`mcp__tavily__tavily_search/crawl/extract/map/research`).
357
+ - **Tools absent from the main-agent whitelist raise no error**: the waterfall filter only matches by name
358
+ against the resolved `assembly.tools`, so a tool that is not mounted (e.g. a commented-out MCP) simply
359
+ never appears — install and runtime both pass silently. Config intent and actual mounts are reconciled by
360
+ install-time detection (`install.sh --auto` / `--dry-run`); re-run it after changing host patches.
361
+ - **Whole-repo exploration tasks should be delegated to `search_internal_deep` rather than done by the
362
+ main agent**: whole-repo grep dumps and full-file reads that repeatedly flood the main context are
363
+ exactly what that role isolates (read-only, no network, condensed summaries only).