@quill507/dsh-orchestrator-preset 0.1.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.
package/install.sh ADDED
@@ -0,0 +1,193 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # install.sh — install the dsh-orchestrator-preset bundle into a DeepSeek Harness home.
4
+ #
5
+ # ./install.sh # install into $DSH_HOME, or ~/.dsh
6
+ # DSH_HOME=/tmp/dsh ./install.sh
7
+ # ./install.sh --check # run the gates and report, change nothing
8
+ #
9
+ # What it does, in order:
10
+ # 0. gate — refuse to continue unless the host is >= 0.1.7-rc.1
11
+ # 1. stage — copy this repository's distributable content to $DSH_HOME/plugins/
12
+ # 2. link — verify the per-profile symlink that `pnpm install` creates
13
+ # 3. skills — verify the four bundled skills; the bundle's own provider serves
14
+ # them, so nothing is copied into the user's global skills directory
15
+ # 4. none — the aegis prefix and its optional description swap ship inside the
16
+ # bundle as extensions/dsh/aegis-prefix.js; there is no companion
17
+ # plugin to install, and installing the old one reintroduces a race
18
+ # 5. report — print the profile wiring the user must apply themselves
19
+ #
20
+ # What it deliberately does NOT do: touch a profile's package.json or cordis.patch.yml.
21
+ # Those are the live installation's own files, and cordis.patch.yml is watched live by
22
+ # dsh-hmr, so writing it triggers an immediate full re-assembly of the running host.
23
+ # Steps 2 and 5 print the exact commands instead.
24
+ #
25
+ # MIT licensed. See ./LICENSE.
26
+
27
+ set -euo pipefail
28
+
29
+ MIN_HOST_VERSION="0.1.7-rc.1"
30
+ BUNDLE_DIRNAME="dsh-orchestrator-preset-bundle"
31
+ BUNDLE_SCOPE="@local/${BUNDLE_DIRNAME}"
32
+ SKILLS="orch-delegation-brief orch-discussion-protocol orch-evidence-protocol orch-real-path-testing"
33
+ CHECK_ONLY=0
34
+ [ "${1:-}" = "--check" ] && CHECK_ONLY=1
35
+
36
+ REPO_ROOT="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
37
+ DSH_HOME="${DSH_HOME:-$HOME/.dsh}"
38
+ if [ ! "$DSH_HOME" = "${DSH_HOME%/}" ]; then DSH_HOME="${DSH_HOME%/}"; fi
39
+
40
+ say() { printf '\n== %s\n' "$*"; }
41
+ info() { printf ' %s\n' "$*"; }
42
+ die() { printf '\n!! %s\n' "$*" >&2; exit 1; }
43
+
44
+ # --- semver compare -------------------------------------------------------
45
+ # Prints -1/0/1 for $1 vs $2. Handles MAJOR.MINOR.PATCH with an optional
46
+ # `-prerelease` suffix; a release outranks its own prereleases.
47
+ ver_cmp() {
48
+ local a_core a_pre b_core b_pre
49
+ a_core="${1%%-*}"; a_pre=""; case "$1" in *-*) a_pre="${1#*-}";; esac
50
+ b_core="${2%%-*}"; b_pre=""; case "$2" in *-*) b_pre="${2#*-}";; esac
51
+ local v
52
+ v="$(printf '%s\n%s\n' "$a_core" "$b_core" | sort -V | head -1)"
53
+ [ "$v" = "$a_core" ] && [ "$a_core" != "$b_core" ] && { echo -1; return; }
54
+ [ "$v" = "$b_core" ] && [ "$a_core" != "$b_core" ] && { echo 1; return; }
55
+ # equal numeric cores -> compare prerelease
56
+ [ -z "$a_pre" ] && [ -z "$b_pre" ] && { echo 0; return; }
57
+ [ -z "$a_pre" ] && { echo 1; return; }
58
+ [ -z "$b_pre" ] && { echo -1; return; }
59
+ [ "$a_pre" = "$b_pre" ] && { echo 0; return; }
60
+ [ "$(printf '%s\n%s\n' "$a_pre" "$b_pre" | sort -V | head -1)" = "$a_pre" ] && { echo -1; return; }
61
+ echo 1
62
+ }
63
+
64
+ # --- 0. host version gate -------------------------------------------------
65
+ say "0. host version gate (need >= ${MIN_HOST_VERSION})"
66
+ HOST_BIN="$(command -v dsh || true)"
67
+ [ -n "$HOST_BIN" ] || die "'dsh' not found on PATH. Install DeepSeek Harness, or re-run with it on PATH."
68
+ HOST_VERSION="$("$HOST_BIN" --version 2>/dev/null | tr -d '[:space:]' | head -1)"
69
+ [ -n "$HOST_VERSION" ] || die "could not read a version from 'dsh --version'."
70
+ info "dsh --version = ${HOST_VERSION}"
71
+ if [ "$(ver_cmp "$HOST_VERSION" "$MIN_HOST_VERSION")" = "-1" ]; then
72
+ die "host ${HOST_VERSION} is older than the required ${MIN_HOST_VERSION}. Refusing to install."
73
+ fi
74
+ info "gate passed"
75
+
76
+ if [ "$CHECK_ONLY" = "1" ]; then
77
+ say "--check: gates only, nothing written."
78
+ exit 0
79
+ fi
80
+
81
+ # --- 1. stage the bundle --------------------------------------------------
82
+ say "1. stage the bundle into ${DSH_HOME}/plugins/${BUNDLE_DIRNAME}"
83
+ DEST="${DSH_HOME}/plugins/${BUNDLE_DIRNAME}"
84
+ if [ -e "$DEST" ]; then
85
+ info "already present — leaving it in place (remove it yourself to reinstall)."
86
+ else
87
+ mkdir -p "$DEST"
88
+ for item in LICENSE README.md README.zh.md cordis.patch.yml package.json personas tools \
89
+ lane-composition.mjs plan-aware-persona.mjs routing-sections.mjs; do
90
+ cp -r "${REPO_ROOT}/${item}" "${DEST}/"
91
+ done
92
+ # The repo root IS the bundle content; these belong in the bundle, not to it.
93
+ info "staged $(find "$DEST" -type f | wc -l) files."
94
+ fi
95
+
96
+ # --- 2. verify the per-profile link --------------------------------------
97
+ # The bundle is resolved BY NAME from a profile's node_modules, so the symlink
98
+ # belongs in each profile — not inside the bundle. `pnpm install` creates it from
99
+ # the profile's `link:` dependency; this step verifies it and says what is missing.
100
+ say "2. verify the ${BUNDLE_SCOPE} symlink in each profile"
101
+ PROFILES="${DSH_HOME}/profiles"
102
+ if [ ! -d "$PROFILES" ]; then
103
+ info "no ${PROFILES} yet — nothing to verify."
104
+ else
105
+ FOUND_PROFILE=0
106
+ for pdir in "$PROFILES"/*/; do
107
+ [ -d "$pdir" ] || continue
108
+ pname="$(basename "$pdir")"
109
+ case "$pname" in node_modules|shared) continue;; esac
110
+ FOUND_PROFILE=1
111
+ link="${pdir}node_modules/@local/${BUNDLE_DIRNAME}"
112
+ if [ -L "$link" ]; then
113
+ # test -L, never test -e: Git Bash can silently degrade a link into a copy,
114
+ # and a copy passes test -e while freezing every later edit to the bundle.
115
+ info "${pname}: real symlink -> $(readlink "$link")"
116
+ elif [ -e "$link" ]; then
117
+ info "${pname}: !! ${link} exists but is NOT a symlink (a copy freezes the bundle)"
118
+ else
119
+ info "${pname}: MISSING — run 'pnpm install' in ${pdir} after adding the link: dependency"
120
+ fi
121
+ done
122
+ [ "$FOUND_PROFILE" = "0" ] && info "no profile directories found."
123
+ fi
124
+
125
+ # --- 3. the four self-authored skills ------------------------------------
126
+ # Nothing to copy. The skills ship inside this bundle and are served by the
127
+ # filesystem skill provider that extensions/dsh/index.js mounts, with
128
+ # includeDefaultRoots:false so the user's global skills directory is left alone.
129
+ # Copying them into $DSH_HOME/skills is what this step used to do, and removing
130
+ # it is the point: a preset should not scatter copies through the user's home.
131
+ say "3. verify the bundled skills (nothing to copy — the bundle's provider serves them)"
132
+ for s in $SKILLS; do
133
+ [ -f "${REPO_ROOT}/skills/${s}/SKILL.md" ] || die "skills/${s}/SKILL.md is missing from the repository."
134
+ want=$(grep -m1 '^name:' "${REPO_ROOT}/skills/${s}/SKILL.md" | sed 's/^name:[[:space:]]*//')
135
+ [ "$want" = "$s" ] || die "skills/${s}: frontmatter name is '$want'; the provider reads the frontmatter, not the directory."
136
+ if [ -d "${DSH_HOME}/skills/${s}" ]; then
137
+ info "${s}: a copy also exists in ${DSH_HOME}/skills — redundant, the provider is authoritative"
138
+ else
139
+ info "${s}: served by the bundle's provider"
140
+ fi
141
+ done
142
+ [ -f "${REPO_ROOT}/extensions/dsh/index.js" ] || die "extensions/dsh/index.js is missing — the bundle has no skill provider."
143
+
144
+ # --- 4. nothing to install ------------------------------------------------
145
+ # The aegis prefix bridge and its optional description swap live in the bundle's
146
+ # own extensions/dsh/aegis-prefix.js, mounted by a row the generator emits. There
147
+ # is no companion plugin, which is deliberate: the two used to be separate
148
+ # plugins, and because both mutate the same skill registrations they raced — the
149
+ # split produced 4 of 22 descriptions localised and two bare names leaking back.
150
+ # One plugin doing both in a single registration pass is what removed the race.
151
+ say "4. nothing to install — extensions/dsh/aegis-prefix.js does this inside the bundle"
152
+
153
+ # --- 5. what the user must do by hand ------------------------------------
154
+ say "5. the profile wiring (deliberate — see the header of this script)"
155
+ cat <<EOF
156
+ A profile's own package.json and cordis.patch.yml are the live installation's
157
+ files, and cordis.patch.yml is watched by dsh-hmr: writing it triggers an
158
+ immediate full re-assembly. This script therefore stops here and prints the
159
+ exact edits instead of making them.
160
+
161
+ (a) In ~/.dsh/profiles/<profile>/package.json — add the dependency:
162
+
163
+ "${BUNDLE_SCOPE}": "link:${DSH_HOME}/plugins/${BUNDLE_DIRNAME}"
164
+
165
+ and add the same string to the dsh.profile.bundles array, keeping its
166
+ position. Then run, in that profile directory:
167
+
168
+ pnpm install
169
+
170
+ which creates the real symlink under node_modules/@local/.
171
+
172
+ (b) In ~/.dsh/profiles/<profile>/cordis.patch.yml — select the preset:
173
+
174
+ - id: preset-dsh-orchestrator-preset
175
+ name: '@deepseek-ai/dsh-agent-preset'
176
+ config:
177
+ selectedDefault: dsh-orchestrator-preset
178
+
179
+ A patch REPLACES the whole row rather than deep-merging it, so a row that
180
+ overrides config must restate every key it needs; the snippet above is the
181
+ whole row, not an addition to a larger one.
182
+
183
+ Inspect ~/.dsh/profiles/<profile>/cordis.patch.yml <- decides the preset
184
+ Ignore ~/.dsh/profiles/<profile>/cordis.yml <- empty by design
185
+
186
+ The GUI preset chooser rewrites cordis.patch.yml in place, so a preset picked
187
+ in the GUI overwrites selectedDefault and does not restore itself. Re-apply
188
+ (b) to come back. This script never touches that file, so re-running it is safe.
189
+
190
+ Re-run with --check to re-verify the host gate without writing anything.
191
+ EOF
192
+
193
+ say "done."
@@ -0,0 +1,288 @@
1
+ /**
2
+ * Lane composition for the `dsh-orchestrator-preset` agent preset — child addressing.
3
+ *
4
+ * The preset's nine named `subagent_*` lanes are rows of the preset itself, so
5
+ * each lane keeps its own persona and `toolFilter` (those faces travel on the
6
+ * lane row). What the Team layer takes over is the coordinator's control
7
+ * surface: on the Lead's seat `send_message` / `list_agents` / `interrupt_agent`
8
+ * resolve to the Team implementations, which address a teammate by NAME and
9
+ * cannot reach a `subagent_*` child at all, so a Lead delegating through
10
+ * `subagent_*` lanes has no tool to list, continue, or interrupt them — which is
11
+ * exactly the three actions this plugin supplies, and nothing else.
12
+ *
13
+ * Names are new because `NamedEntries.insert` throws on a duplicate inside one
14
+ * layer (`dsh-scope/lib/index.js:27-29`) and `tool-agent-team` registers its ten
15
+ * names under a SINGLE try/catch over its whole set: reusing the Team names
16
+ * would collide in the Lead's own layer and unwind every Team tool, shared task
17
+ * board included.
18
+ *
19
+ * The tools are registered GLOBALLY — `ctx.tools.register`, once, at mount — and
20
+ * never into a per-agent scope. Two measured reasons. First, `tools.restrict()`
21
+ * accepts only GLOBALLY registered names: a scoped registration makes every
22
+ * lane's deny entry illegal (`tools.restrict() names unknown global tools …`) and
23
+ * the lane's whole setup then throws, which is how this plugin once took the
24
+ * entire lane family down. Second, the preset's own control tools
25
+ * (`send_message` / `list_agents` / `interrupt_agent`) already have exactly this
26
+ * shape: registered globally by `tool-subagent-control`, then denied per lane by
27
+ * the same deny lists. Those lists are what keeps these three names off a lane's
28
+ * seat; the Lead, not being a lane, keeps them.
29
+ *
30
+ * Each action passes the live calling Agent (`exec.agent`) as sender/authority and
31
+ * leaves ownership to the service (`agents.isOwnedBy` is not a gate: it accepts
32
+ * only live resident children and would reject cold resume). One residual: the
33
+ * adjacent-agent resume pointer is not injected under the Team assembly, so that
34
+ * contract is carried by the persona and the `orch-delegation-brief` skill instead.
35
+ *
36
+ * @module lane-composition
37
+ */
38
+
39
+ import { defineTool } from '@deepseek-ai/dsh-tools'
40
+
41
+ /** Cordis plugin name. */
42
+ export const name = 'lane-composition'
43
+
44
+ /**
45
+ * Three services, and the first is not optional: `tools` is the registry this
46
+ * plugin registers INTO — reading `ctx.tools` without it throws `cannot get
47
+ * property "tools" without inject` (cordis' context proxy), which an earlier
48
+ * revision caught into a warning and thereby registered nothing at all. The
49
+ * sibling row `@deepseek-ai/dsh-tool-subagent-control` declares `tools` on this
50
+ * same plane: the precedent that it is reachable here.
51
+ *
52
+ * `subagents` is the continuation seam and its catalog; `agents` is the live Agent
53
+ * registry that turns a durable child id into a status. Both come from `dsh-base`.
54
+ * No Team service is declared or read: registration is decision-free (no
55
+ * membership test, no per-agent hook), so an assembly WITHOUT the Team bundle
56
+ * behaves exactly like one with it.
57
+ */
58
+ export const inject = ['tools', 'subagents', 'agents']
59
+
60
+ /**
61
+ * Refine one child's status through the live Agent registry: `running` for an
62
+ * active driver, `idle` for a resident Agent between turns, and `ready` when no
63
+ * live Agent remains. `ready` keeps resumability visible without presenting an
64
+ * inactive conversation as terminal.
65
+ *
66
+ * @param agents - the live Agent registry (`ctx.agents`).
67
+ * @param id - the child's durable session id.
68
+ * @returns the status to report for that child.
69
+ */
70
+ function childStatus(agents, id) {
71
+ const live = agents.get(id)
72
+ if (live === undefined) return 'ready'
73
+ return live.status === 'running' ? 'running' : 'idle'
74
+ }
75
+
76
+ /**
77
+ * Project one catalog entry into the model-facing row, or drop a one-shot child.
78
+ *
79
+ * A child whose `mode` is not `continuable` can never accept a message, so
80
+ * offering it would only invite an unaddressable id. The label falls back to the
81
+ * id because the schema promises a string and the catalog records `label` as
82
+ * optional.
83
+ *
84
+ * @param agents - the live Agent registry (`ctx.agents`).
85
+ * @param entry - one row from `listChildren`.
86
+ * @returns the row to report, or undefined for a one-shot child.
87
+ */
88
+ function projectChild(agents, entry) {
89
+ if (entry.mode !== 'continuable') return undefined
90
+ return {
91
+ id: entry.id,
92
+ label: entry.label ?? entry.id,
93
+ status: childStatus(agents, entry.id),
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Build the Lead-side `subagent_children` tool: enumeration of the caller's own
99
+ * continuable `subagent_*` children by durable agent id.
100
+ *
101
+ * The listing is scoped CONSTRUCTIVELY — it queries the caller's own session id
102
+ * — so every row is necessarily one of the caller's own children.
103
+ *
104
+ * @param ctx - the preset's scope context, where `subagents` and `agents` were injected.
105
+ * @returns the tool definition.
106
+ */
107
+ function childrenTool(ctx) {
108
+ return defineTool({
109
+ name: 'subagent_children',
110
+ description: 'List YOUR OWN continuable `subagent_*` children by durable agent id, label, and live status. '
111
+ + '`running` means the child is working now; `idle` means it is resident but between turns; `ready` means no '
112
+ + 'live Agent remains although the durable session still exists, so it can still be resumed. The `id` reported '
113
+ + 'here is exactly the `agent_id` that `subagent_send` and `subagent_interrupt` take. This is NOT the Team '
114
+ + 'roster: `list_agents` lists the Lead and every durable teammate by teammate NAME and cannot address a '
115
+ + '`subagent_*` child at all. One-shot children are omitted because they cannot be messaged.',
116
+ parameters: {},
117
+ output: {
118
+ schema: {
119
+ type: 'array',
120
+ items: {
121
+ type: 'object',
122
+ additionalProperties: false,
123
+ properties: {
124
+ id: { type: 'string', required: true },
125
+ label: { type: 'string', required: true },
126
+ status: { type: 'string', required: true, enum: ['running', 'idle', 'ready'] },
127
+ },
128
+ },
129
+ },
130
+ render: (_args, entries) => [{
131
+ type: 'text',
132
+ text: entries.length === 0
133
+ ? '(no subagent children)'
134
+ : entries.map((entry) => `${entry.id} [${entry.status}] — ${entry.label}`).join('\n'),
135
+ }],
136
+ },
137
+ async execute(_args, exec) {
138
+ const parent = exec.agent
139
+ if (parent === undefined) throw new Error('subagent_children requires a calling agent (exec.agent was undefined)')
140
+ const entries = await ctx.subagents.listChildren(parent.session.header.id, exec.signal)
141
+ return entries.map((entry) => projectChild(ctx.agents, entry)).filter((entry) => entry !== undefined)
142
+ },
143
+ })
144
+ }
145
+
146
+ /**
147
+ * Build the Lead-side `subagent_send` tool: continuation delivery to one of the
148
+ * caller's own `subagent_*` children by durable agent id.
149
+ *
150
+ * @param ctx - the preset's scope context, where `subagents` was injected.
151
+ * @returns the tool definition.
152
+ */
153
+ function sendTool(ctx) {
154
+ return defineTool({
155
+ name: 'subagent_send',
156
+ description: 'Deliver a message to a DIRECT continuable child of your own, addressed by the agent id that '
157
+ + '`subagent_children` reported. A child that is still working is steered at its nearest step boundary; an '
158
+ + 'idle child starts a turn; a child that is no longer resident is cold-resumed from its durable session, '
159
+ + 'which is what makes this the continuation tool. The call returns only confirmation that the message was '
160
+ + 'DELIVERED — never the child\'s reply — so a failure means the message was NOT delivered. This is NOT the '
161
+ + 'Team tool: `send_message` addresses a teammate by NAME and cannot reach a `subagent_*` child at all.',
162
+ parameters: {
163
+ agent_id: {
164
+ type: 'string',
165
+ required: true,
166
+ description: 'The durable agent id of your direct continuable `subagent_*` child, as reported by `subagent_children`.',
167
+ },
168
+ message: {
169
+ type: 'string',
170
+ required: true,
171
+ description: 'The message to deliver to the child.',
172
+ },
173
+ },
174
+ output: {
175
+ schema: {
176
+ type: 'object',
177
+ additionalProperties: false,
178
+ properties: { messageId: { type: 'string', required: true } },
179
+ },
180
+ render: (args, _value) => [{ type: 'text', text: `message delivered to agent ${args.agent_id}` }],
181
+ },
182
+ async execute(args, exec) {
183
+ const sender = exec.agent
184
+ if (sender === undefined) throw new Error('subagent_send requires a calling agent (exec.agent was undefined)')
185
+ const content = [{ type: 'text', text: args.message }]
186
+ return { messageId: await ctx.subagents.sendMessage(sender, args.agent_id, content, { signal: exec.signal }) }
187
+ },
188
+ })
189
+ }
190
+
191
+ /**
192
+ * Build the Lead-side `subagent_interrupt` tool: cancellation of one running
193
+ * `subagent_*` child's CURRENT TURN by durable agent id.
194
+ *
195
+ * @param ctx - the preset's scope context, where `subagents` was injected.
196
+ * @returns the tool definition.
197
+ */
198
+ function interruptTool(ctx) {
199
+ return defineTool({
200
+ name: 'subagent_interrupt',
201
+ description: 'Request cancellation of one running `subagent_*` child\'s CURRENT TURN, addressed by its agent id. '
202
+ + 'Only the current turn stops: messages already queued for the child stay parked until a later '
203
+ + '`subagent_send`, agents the child started keep running, and the child itself stays available for '
204
+ + 'follow-ups. The call returns as soon as the stop request is accepted, so the target may keep running '
205
+ + 'briefly, and interrupting an already-finished agent is an accepted no-op. Address DIRECT children only; to '
206
+ + 'stop a grandchild, have the intermediate child interrupt it. This is NOT the Team tool: `interrupt_agent` '
207
+ + 'addresses a teammate by name and cannot reach a `subagent_*` child at all.',
208
+ parameters: {
209
+ agent_id: {
210
+ type: 'string',
211
+ required: true,
212
+ description: 'The durable agent id of the running `subagent_*` child whose current turn should stop.',
213
+ },
214
+ },
215
+ output: {
216
+ schema: {
217
+ type: 'object',
218
+ additionalProperties: false,
219
+ properties: { accepted: { type: 'boolean', required: true } },
220
+ },
221
+ render: (args, _value) => [{ type: 'text', text: `interrupt requested for agent ${args.agent_id}` }],
222
+ },
223
+ async execute(args, exec) {
224
+ const caller = exec.agent
225
+ if (caller === undefined) throw new Error('subagent_interrupt requires a calling agent (exec.agent was undefined)')
226
+ await ctx.subagents.interrupt(args.agent_id, { kind: 'ancestor', agent: caller })
227
+ return { accepted: true }
228
+ },
229
+ })
230
+ }
231
+
232
+ /**
233
+ * Build the three Lead-side child-addressing tools.
234
+ *
235
+ * Exported so the definitions can be constructed and inspected without a live
236
+ * host: `defineTool` compiles every schema eagerly, so building them IS the
237
+ * check that the declarations are valid.
238
+ *
239
+ * @param ctx - a context carrying the `subagents` and `agents` services.
240
+ * @returns the enumeration, delivery, and cancellation tools.
241
+ */
242
+ export function buildChildTools(ctx) {
243
+ return { children: childrenTool(ctx), send: sendTool(ctx), interrupt: interruptTool(ctx) }
244
+ }
245
+
246
+ /**
247
+ * Register the three tools into the GLOBAL layer, once, at mount.
248
+ *
249
+ * Global is not a preference: `tools.restrict()` resolves its names against the
250
+ * global registry, so a lane's deny entry is legal only if the name was
251
+ * registered here (see the module doc). Registration therefore depends on no
252
+ * agent, no membership, and no Team service.
253
+ *
254
+ * A failure is still contained: a throwing register must not take the preset
255
+ * down with it, and the warning names the error so a silent feature outage is
256
+ * still visible in the log.
257
+ *
258
+ * @param ctx - the preset's scope context, where the injected services live.
259
+ * @returns the three tool names and whether registration succeeded.
260
+ */
261
+ function applyGlobalTools(ctx) {
262
+ const definitions = Object.values(buildChildTools(ctx))
263
+ const tools = definitions.map((tool) => tool.name)
264
+ try {
265
+ for (const definition of definitions) ctx.tools.register(definition)
266
+ return { tools, registered: true }
267
+ } catch (error) {
268
+ ctx.logger.warn(`lane-composition: child-addressing tools failed to register: ${String(error)}`)
269
+ return { tools, registered: false }
270
+ }
271
+ }
272
+
273
+ /**
274
+ * Register the child-addressing tools once, globally, when this row mounts.
275
+ *
276
+ * Every agent then inherits them, which is why the preset's lane deny lists name
277
+ * all three: the lists are what keeps the names off a lane's seat while the Lead
278
+ * keeps them. Nothing here waits on, reads, or depends on the Team bundle.
279
+ *
280
+ * @param ctx - the preset's scope context.
281
+ */
282
+ export function apply(ctx) {
283
+ const registration = applyGlobalTools(ctx)
284
+ ctx.logger.info(
285
+ `lane-composition: registered ${JSON.stringify(registration.tools)} registered=${registration.registered}`,
286
+ )
287
+ }
288
+
package/package.json ADDED
@@ -0,0 +1,69 @@
1
+ {
2
+ "name": "@quill507/dsh-orchestrator-preset",
3
+ "version": "0.1.0",
4
+ "description": "A thin-shell agent preset for DeepSeek Harness: nine named lanes with declared tool boundaries, resident routing sections, four self-authored skills, and an evidence-based completion gate.",
5
+ "keywords": [
6
+ "deepseek-harness",
7
+ "dsh",
8
+ "agent-preset",
9
+ "orchestration",
10
+ "aegis"
11
+ ],
12
+ "license": "MIT",
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "git+https://github.com/cuddly-guacamole/dsh-orchestrator-preset.git"
16
+ },
17
+ "homepage": "https://github.com/cuddly-guacamole/dsh-orchestrator-preset#readme",
18
+ "bugs": {
19
+ "url": "https://github.com/cuddly-guacamole/dsh-orchestrator-preset/issues"
20
+ },
21
+ "publishConfig": {
22
+ "access": "public"
23
+ },
24
+ "type": "module",
25
+ "peerDependencies": {
26
+ "@deepseek-ai/cordis": ">=4.0.1 <5",
27
+ "@deepseek-ai/dsh-skill-filesystem": ">=0.2.0-rc.2 <2",
28
+ "@deepseek-ai/dsh-tools": ">=0.2.0-rc.2 <2",
29
+ "@deepseek-ai/schemastery": ">=3.18.0 <4"
30
+ },
31
+ "peerDependenciesMeta": {
32
+ "@deepseek-ai/cordis": {
33
+ "optional": true
34
+ },
35
+ "@deepseek-ai/dsh-skill-filesystem": {
36
+ "optional": true
37
+ },
38
+ "@deepseek-ai/dsh-tools": {
39
+ "optional": true
40
+ },
41
+ "@deepseek-ai/schemastery": {
42
+ "optional": true
43
+ }
44
+ },
45
+ "exports": {
46
+ "./plan-aware-persona.mjs": "./plan-aware-persona.mjs",
47
+ "./routing-sections.mjs": "./routing-sections.mjs",
48
+ "./lane-composition.mjs": "./lane-composition.mjs",
49
+ "./personas/analyst.md": "./personas/analyst.md",
50
+ "./personas/archivist.md": "./personas/archivist.md",
51
+ "./personas/auditor.md": "./personas/auditor.md",
52
+ "./personas/forge.md": "./personas/forge.md",
53
+ "./personas/orchestrator.md": "./personas/orchestrator.md",
54
+ "./personas/planner.md": "./personas/planner.md",
55
+ "./personas/reader.md": "./personas/reader.md",
56
+ "./personas/scout.md": "./personas/scout.md",
57
+ "./personas/seer.md": "./personas/seer.md",
58
+ "./personas/wright.md": "./personas/wright.md",
59
+ "./cordis.patch.yml": "./cordis.patch.yml",
60
+ "./extensions/dsh/index.js": "./extensions/dsh/index.js",
61
+ "./extensions/dsh/aegis-prefix.js": "./extensions/dsh/aegis-prefix.js",
62
+ "./package.json": "./package.json"
63
+ },
64
+ "dsh": {
65
+ "bundle": {
66
+ "patch": "./cordis.patch.yml"
67
+ }
68
+ }
69
+ }
@@ -0,0 +1,74 @@
1
+ <agent-identity>
2
+ Your designated identity for this session is Analyst. This identity supersedes any prior identity statement.
3
+ Analyst — pre-planning analysis of intent, ambiguity and scope.
4
+ </agent-identity>
5
+
6
+ You run before a plan exists. Your output is not a description of the request — it is the set of instructions a Planner can execute: what this work really is, what must be decided, and what would prove it done.
7
+
8
+ ## Charter
9
+
10
+ | Item | Value |
11
+ |---|---|
12
+ | Raised by | A coordinator dispatch to `subagent_analyst`, before planning starts. |
13
+ | Produces | Executable instructions for the Planner: scope, decisions, and acceptance checks. |
14
+ | Never produces | The plan itself, implementation, or a restatement of the request in longer words. |
15
+ | Reads | The request, the affected tree, and any evidence the coordinator supplied. |
16
+ | Ends when | The intent is classified, the open decisions are named, and the acceptance checks are written. |
17
+
18
+ ## Method
19
+
20
+ Classify the intent first, because the classification decides which analysis is worth doing at all.
21
+
22
+ | Intent class | The analysis this class needs |
23
+ |---|---|
24
+ | Build something new | Where it will live, what it must interoperate with, and the smallest version that is still useful |
25
+ | Change something existing | The current behaviour, the callers that depend on it, and what must stay true |
26
+ | Explain or diagnose a failure | The observed symptom, the reproduction, and the boundary between symptom and cause |
27
+ | Choose between options | The comparison criteria, and the fact that would settle it |
28
+ | Produce a document or decision | The audience, the decision it supports, and the evidence standard it must meet |
29
+ | Unclear | Say which two classes it might be, and ask the one question that separates them |
30
+
31
+ | Practice | Why |
32
+ |---|---|
33
+ | Turn observations into instructions | "The module has no tests" is an observation; "add a regression test covering the failing case before changing the parser" is an instruction. |
34
+ | Ask only questions that change the plan | A question whose answer cannot alter any task is a question that costs a turn and buys nothing. |
35
+ | Name the decisions the Planner must make | Unnamed decisions get made silently by whoever implements, which is how scope drifts. |
36
+ | Bound the scope explicitly | Say what this work will not touch; silence about scope is read as permission. |
37
+ | Write acceptance before implementation | Each instruction carries the check that would show it done, so the plan cannot end in a subjective finish. |
38
+ | Separate assumption from fact | Label each assumption with the check that would test it, and keep the two visibly apart. |
39
+ | Name the failure path, not only the happy one | Most plans break on the input nobody described, so the analysis should describe it first. |
40
+ | Say what would make the work unnecessary | If a cheaper answer exists, the planner should hear it now rather than after the plan is written. |
41
+
42
+ ## Deliverable
43
+
44
+ | Part | Content |
45
+ |---|---|
46
+ | Intent | The class from the table above, with one sentence of justification |
47
+ | Scope | In scope, out of scope, and the boundary that decided it |
48
+ | Instructions | Ordered, imperative lines for the Planner — each one a thing to do, not a thing to notice |
49
+ | Decisions | Each decision the plan must make, with the options and the evidence for each |
50
+ | Acceptance | The checks that prove the work done, including the failure path, not only the happy path |
51
+ | Questions | The few questions that genuinely change the plan, each with why it matters |
52
+ | Reuse | Existing material the work should build on, so the plan does not invent a second version of it |
53
+
54
+ ## Reporting as a lane
55
+
56
+ | Part | Content |
57
+ |---|---|
58
+ | Result | The intent class and the one-sentence scope |
59
+ | Basis | What was inspected to reach it |
60
+ | Assumptions | Each assumption with the check that would test it |
61
+ | Blockers | Decisions that cannot proceed without the user, named so the coordinator can ask once |
62
+
63
+ ## Boundaries
64
+
65
+ Read-only: this lane analyses the request and the tree it will touch, and its only written artifact is the evidence file for its own task.
66
+
67
+ ## Stop conditions
68
+
69
+ | Condition | Response |
70
+ |---|---|
71
+ | Intent classified, decisions named, acceptance written | Report and stop |
72
+ | The request is ambiguous between two intents | Return the single separating question rather than planning for both |
73
+ | The scope cannot be bounded from what is available | Say which boundary fact is missing, and stop |
74
+ | The work has grown beyond the request | Say so; expanding scope is the coordinator's call, not this lane's |