dsh-model-router 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 dsh-model-router contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,94 @@
1
+ # dsh-model-router
2
+
3
+ A small plugin for the [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) that stops treating every model call the same. It splits your session into two roles:
4
+
5
+ - **The planner** — your main agent — always runs on `deepseek-v4-pro`. That's where the thinking happens: understanding what you want, designing the approach, reviewing results, writing the final answer.
6
+ - **The executors** — every subagent it delegates to — always run on `deepseek-v4-flash`. That's where the work happens: writing code, running commands, iterating on builds.
7
+
8
+ The idea is simple: pro is the better thinker, flash is fast and cheap at grinding through implementation. You get the careful planning of the big model without paying pro prices for every single tool call.
9
+
10
+ ## Install
11
+
12
+ Add it to a profile (this installs into the `web` profile; change the name for another one):
13
+
14
+ ```bash
15
+ dsh plugin --profile web add git+https://github.com/thedeveloper256/dsh-model-router
16
+ ```
17
+
18
+ That's a git install, so pnpm clones the repo and builds it on the spot. pnpm guards build scripts by default, though — it'll print an `allowBuilds` key you need to add to the profile's `pnpm-workspace.yaml`, then re-run the same `dsh plugin` command. It's a one-time thing:
19
+
20
+ ```yaml
21
+ allowBuilds:
22
+ dsh-model-router@git+https://github.com/thedeveloper256/dsh-model-router#<commit>: true
23
+ ```
24
+
25
+ Once it's in, restart the profile. You should see the row under `model-router` in `dsh web --dump-config`.
26
+
27
+ ## What it actually does
28
+
29
+ Three small surfaces, one rule:
30
+
31
+ 1. **Request routing** — every model request gets stamped with a role. Root agents get `deepseek-v4-pro`; delegation children (`subagent`, `subagent_fork`, workflow workers, ralph rounds) get `deepseek-v4-flash`. The rewrite sits at the outermost layer of the request pipeline, so it wins — even over the harness's own default model (which is `deepseek-v4-flash` out of the box) and over whatever model you pick in the UI for the session. That's intentional: it's the "enforce" knob.
32
+ 2. **A prompt section** — a short note that renders before the agent's persona, telling the planner: you're the thinker, delegate the implementation. Without this, the model tends to just do everything itself.
33
+ 3. **A skill** — the `pro-flash-routing` skill shows up in the session's skill catalog and spells out the working rhythm: plan, delegate, review, report. Same convention, but loadable on demand when the agent wants details.
34
+
35
+ ## How the roles are decided
36
+
37
+ An agent is an *executor* if it carries either of the markers the harness stamps on delegation children:
38
+
39
+ - `options.subagentDepth >= 1`, or
40
+ - `session.header.origin === "subagent"`
41
+
42
+ Everything else is a planner. That logic lives in `src/policy.ts` as a plain function, so it's easy to reason about and test.
43
+
44
+ ### A behavior worth knowing
45
+
46
+ The router only overrides the model (`provider` + `model`), not the rest of the request. So reasoning effort and the other sampling settings still come from your session's selection — pick "max effort" and you get pro/flash doing max effort, just not a different model. It's "which model runs" that's enforced, not "how hard it thinks".
47
+
48
+ ## Tuning
49
+
50
+ All configuration lives on the plugin row. If you want different models, or you'd rather the prompt section or skill not be registered, patch the row in the profile's `cordis.patch.yml`:
51
+
52
+ ```yaml
53
+ - patch:
54
+ - id: model-router
55
+ config:
56
+ planner: # root-agent route
57
+ provider: deepseek-official
58
+ model: deepseek-v4-pro
59
+ executor: # subagent route
60
+ provider: deepseek-official
61
+ model: deepseek-v4-flash
62
+ promptSection: true # register the always-on routing section
63
+ skill: true # register the pro-flash-routing skill
64
+ ```
65
+
66
+ The defaults are exactly the table at the top of this page. To switch the router off for a session, disable the row (`disabled: true`) or remove the plugin — `dsh plugin --profile web remove dsh-model-router`.
67
+
68
+ ## Does it work?
69
+
70
+ I verified it against a real session log. Run a task that makes the agent plan and delegate, then check which models actually made the requests:
71
+
72
+ ```bash
73
+ zstd -d -c "$DSH_HOME"/sessions/<workspace>/<session>/session.jsonl.zstd \
74
+ | grep -o '"model":"deepseek-v4-[a-z]*"' | sort | uniq -c
75
+ ```
76
+
77
+ Planner messages come back as `deepseek-v4-pro`; subagent messages as `deepseek-v4-flash`. In my test: 9 pro requests in the planner's session, 6 flash in the subagent's.
78
+
79
+ ## Development
80
+
81
+ It's a normal small TypeScript package — no framework magic:
82
+
83
+ ```bash
84
+ npm install
85
+ npm run typecheck
86
+ npm test
87
+ npm run build
88
+ ```
89
+
90
+ The `prepare` script builds `lib/` automatically, which is what makes the git install work without shipping build artifacts in the repo. The `dsh.bundle` field in `package.json` is what tells `dsh plugin` how to compose the plugin into a profile.
91
+
92
+ ## License
93
+
94
+ MIT
@@ -0,0 +1,23 @@
1
+ # dsh-model-router bundle patch.
2
+ #
3
+ # Inserted after the shipped bundle layers of the profile it is installed
4
+ # into, so it overrides nothing but adds one row: the router itself. All
5
+ # routing decisions live in the plugin's config here — there are no other
6
+ # rows to patch because the `agent/request` rewrite applies to every agent
7
+ # regardless of which preset or mode mounts the delegation tools.
8
+ - insert:
9
+ - id: model-router
10
+ name: 'dsh-model-router'
11
+ config:
12
+ # The session's root agent: planning, design, review, synthesis.
13
+ planner:
14
+ provider: deepseek-official
15
+ model: deepseek-v4-pro
16
+ # Every delegated subagent: code writing and execution.
17
+ executor:
18
+ provider: deepseek-official
19
+ model: deepseek-v4-flash
20
+ # Publish the always-on routing convention prompt section.
21
+ promptSection: true
22
+ # Register the `pro-flash-routing` skill in the session catalog.
23
+ skill: true
package/lib/index.d.ts ADDED
@@ -0,0 +1,113 @@
1
+ import { Service, Context } from '@deepseek-ai/cordis';
2
+ import z from '@deepseek-ai/schemastery';
3
+
4
+ /**
5
+ * Pure routing policy for dsh-model-router: which model each agent role gets.
6
+ * Kept free of Cordis imports so the policy is trivially unit-testable.
7
+ * @module dsh-model-router/policy
8
+ */
9
+ /** One route: a provider/model pair stamped onto an agent request. */
10
+ interface ModelRoute {
11
+ provider: string;
12
+ model: string;
13
+ }
14
+ /** The two roles the router distinguishes. */
15
+ type AgentRole = "planner" | "executor";
16
+ /** Resolved router configuration: one route per role. */
17
+ interface RouterConfig {
18
+ planner: ModelRoute;
19
+ executor: ModelRoute;
20
+ promptSection: boolean;
21
+ skill: boolean;
22
+ }
23
+ /**
24
+ * Classify an agent as planner or executor.
25
+ *
26
+ * The main (root) agent of a session is the planner. Every agent created as a
27
+ * delegation child — `subagent`, `subagent_fork`, workflow workers, ralph
28
+ * rounds — is an executor. The harness stamps two durable facts on children:
29
+ * `options.subagentDepth` (>= 1) and the session header `origin: "subagent"`.
30
+ *
31
+ * @param agent - the live agent (any subset of the runtime shape).
32
+ * @returns the role the agent should be routed as.
33
+ */
34
+ declare function roleFor(agent: unknown): AgentRole;
35
+ /**
36
+ * Resolve the route for one agent.
37
+ * @param agent - the live agent.
38
+ * @param config - the resolved router configuration.
39
+ * @returns the model route to stamp, or `undefined` to leave the request alone.
40
+ */
41
+ declare function routeFor(agent: unknown, config: RouterConfig): ModelRoute | undefined;
42
+
43
+ /**
44
+ * dsh-model-router: role-based model routing for the DeepSeek Harness.
45
+ *
46
+ * The planner (the session's root agent) runs on `deepseek-v4-pro`; delegated
47
+ * executor subagents run on `deepseek-v4-flash`. Enforcement is a per-agent
48
+ * `agent/request` rewrite registered when the agent is created, so it applies
49
+ * in every mode (web / headless / tui) and every agent preset, including
50
+ * subagents the delegation tools create.
51
+ *
52
+ * The plugin also publishes:
53
+ * - a system-prompt section stating the planner/executor convention, and
54
+ * - the `pro-flash-routing` skill teaching the agent to plan itself and
55
+ * delegate code execution to flash subagents.
56
+ *
57
+ * @module dsh-model-router
58
+ */
59
+
60
+ /** Plugin row id; the bundle patch inserts it under this id. */
61
+ declare const name = "model-router";
62
+ /** The plugin's public config, validated at row load. */
63
+ declare const Config: z<Schemastery.ObjectS<{
64
+ planner: z<Schemastery.ObjectS<{
65
+ provider: z<string, string>;
66
+ model: z<string, string>;
67
+ }>, Schemastery.ObjectT<{
68
+ provider: z<string, string>;
69
+ model: z<string, string>;
70
+ }>>;
71
+ executor: z<Schemastery.ObjectS<{
72
+ provider: z<string, string>;
73
+ model: z<string, string>;
74
+ }>, Schemastery.ObjectT<{
75
+ provider: z<string, string>;
76
+ model: z<string, string>;
77
+ }>>;
78
+ promptSection: z<boolean, boolean>;
79
+ skill: z<boolean, boolean>;
80
+ }>, Schemastery.ObjectT<{
81
+ planner: z<Schemastery.ObjectS<{
82
+ provider: z<string, string>;
83
+ model: z<string, string>;
84
+ }>, Schemastery.ObjectT<{
85
+ provider: z<string, string>;
86
+ model: z<string, string>;
87
+ }>>;
88
+ executor: z<Schemastery.ObjectS<{
89
+ provider: z<string, string>;
90
+ model: z<string, string>;
91
+ }>, Schemastery.ObjectT<{
92
+ provider: z<string, string>;
93
+ model: z<string, string>;
94
+ }>>;
95
+ promptSection: z<boolean, boolean>;
96
+ skill: z<boolean, boolean>;
97
+ }>>;
98
+ declare const SKILL_NAME = "pro-flash-routing";
99
+ declare const SKILL_DESCRIPTION = "Route planning and code execution across models: plan on the pro planner agent, delegate implementation to flash executor subagents.";
100
+ declare const SKILL_WHEN_TO_USE = "Use when a task combines planning and implementation: before writing code, after a plan is approved, when delegating execution work, or when the user asks about the pro/flash routing convention.";
101
+ declare const SKILL_CONTENT = "# Pro planner / Flash executor routing\n\nThis session routes models by role:\n\n- **Planner (this agent)** \u2014 `deepseek-v4-pro`. Planning, design decisions, reviewing delegated output, and user-facing synthesis happen here.\n- **Executors (every subagent)** \u2014 `deepseek-v4-flash`. Implementation work happens there: writing code, running commands, builds, and tests. The harness forces the model automatically; you do not select it.\n\n## Working rhythm\n\n1. **Plan here.** Explore, decide the approach, and (when plan mode is on) submit the plan with `exit_plan_mode`. The plan stays on this agent.\n2. **Delegate the execution.** Once a plan is approved, hand each self-contained chunk of implementation to a subagent with a complete prompt: exact files to touch, the change to make, and how to verify. Subagents are automatically routed to `deepseek-v4-flash`, so keep them execution-focused: give them the decision, not the decision to make.\n3. **Review here.** Read the subagent's result on this agent, verify it yourself (tests, diffs, logs), and iterate with follow-up messages to the same subagent when available.\n4. **Report here.** Summaries, plans, and answers to the user come from this agent.\n\n## Delegation guidelines\n\n- Start independent delegations together in one assistant message and continue useful work while they run (background mode by default).\n- Prefer `subagent` for self-contained work and `workflow` when many independent pieces need fan-out; their workers run on flash as well.\n- Do not delegate design: subagents execute decisions already made.\n- If a subagent's task grows into design work, pull it back to this agent and re-delegate the narrowed execution.\n\n## Verification\n\n- Executor output was produced by `deepseek-v4-flash`; planner output by `deepseek-v4-pro`. If you need to confirm, check the session log's model metadata.\n- If routing ever looks wrong, the `model-router` plugin row in the profile composition is the single place that owns it.";
102
+ /** The plugin row id the bundle patch must insert. */
103
+ declare const ROW_ID = "model-router";
104
+ /**
105
+ * Cordis service: per-agent request routing plus the convention surface.
106
+ */
107
+ declare class ModelRouter extends Service {
108
+ static inject: string[];
109
+ config: RouterConfig;
110
+ constructor(ctx: Context, rawConfig?: unknown);
111
+ }
112
+
113
+ export { type AgentRole, Config, type ModelRoute, ModelRouter, ROW_ID, type RouterConfig, SKILL_CONTENT, SKILL_DESCRIPTION, SKILL_NAME, SKILL_WHEN_TO_USE, ModelRouter as default, name, roleFor, routeFor };
package/lib/index.js ADDED
@@ -0,0 +1,133 @@
1
+ // src/index.ts
2
+ import { Service } from "@deepseek-ai/cordis";
3
+ import z from "@deepseek-ai/schemastery";
4
+
5
+ // src/policy.ts
6
+ function roleFor(agent) {
7
+ const options = agent?.options;
8
+ const depth = options?.subagentDepth;
9
+ if (typeof depth === "number" && depth >= 1) return "executor";
10
+ const session = agent?.session;
11
+ const origin = session?.header ? session.header.origin : void 0;
12
+ if (origin === "subagent") return "executor";
13
+ return "planner";
14
+ }
15
+ function routeFor(agent, config) {
16
+ return config[roleFor(agent)];
17
+ }
18
+
19
+ // src/index.ts
20
+ var name = "model-router";
21
+ var ModelRouteSchema = z.object({
22
+ provider: z.string().min(1),
23
+ model: z.string().min(1)
24
+ });
25
+ var Config = z.object({
26
+ planner: ModelRouteSchema.default({
27
+ provider: "deepseek-official",
28
+ model: "deepseek-v4-pro"
29
+ }),
30
+ executor: ModelRouteSchema.default({
31
+ provider: "deepseek-official",
32
+ model: "deepseek-v4-flash"
33
+ }),
34
+ promptSection: z.boolean().default(true),
35
+ skill: z.boolean().default(true)
36
+ });
37
+ function resolveConfig(raw) {
38
+ const parsed = Config(raw ?? {});
39
+ return {
40
+ planner: parsed.planner,
41
+ executor: parsed.executor,
42
+ promptSection: parsed.promptSection,
43
+ skill: parsed.skill
44
+ };
45
+ }
46
+ var SECTION_ORDER = -50;
47
+ var SECTION_TEXT = `Model routing is role-based in this session. You are the planner and run on {PLANNER_MODEL}. Do your own planning, design, review of delegated output, and user-facing synthesis on this agent. Code execution runs on {EXECUTOR_MODEL}: after a plan is approved, delegate implementation work \u2014 writing code, running commands, builds, and tests \u2014 to subagents, which are automatically routed to {EXECUTOR_MODEL}. Give each subagent a complete, self-contained prompt and prefer background delegation for independent work. Do not hand-write large amounts of code or run long executions on this planner agent; delegate instead.`;
48
+ var SKILL_NAME = "pro-flash-routing";
49
+ var SKILL_DESCRIPTION = "Route planning and code execution across models: plan on the pro planner agent, delegate implementation to flash executor subagents.";
50
+ var SKILL_WHEN_TO_USE = `Use when a task combines planning and implementation: before writing code, after a plan is approved, when delegating execution work, or when the user asks about the pro/flash routing convention.`;
51
+ var SKILL_CONTENT = `# Pro planner / Flash executor routing
52
+
53
+ This session routes models by role:
54
+
55
+ - **Planner (this agent)** \u2014 \`deepseek-v4-pro\`. Planning, design decisions, reviewing delegated output, and user-facing synthesis happen here.
56
+ - **Executors (every subagent)** \u2014 \`deepseek-v4-flash\`. Implementation work happens there: writing code, running commands, builds, and tests. The harness forces the model automatically; you do not select it.
57
+
58
+ ## Working rhythm
59
+
60
+ 1. **Plan here.** Explore, decide the approach, and (when plan mode is on) submit the plan with \`exit_plan_mode\`. The plan stays on this agent.
61
+ 2. **Delegate the execution.** Once a plan is approved, hand each self-contained chunk of implementation to a subagent with a complete prompt: exact files to touch, the change to make, and how to verify. Subagents are automatically routed to \`deepseek-v4-flash\`, so keep them execution-focused: give them the decision, not the decision to make.
62
+ 3. **Review here.** Read the subagent's result on this agent, verify it yourself (tests, diffs, logs), and iterate with follow-up messages to the same subagent when available.
63
+ 4. **Report here.** Summaries, plans, and answers to the user come from this agent.
64
+
65
+ ## Delegation guidelines
66
+
67
+ - Start independent delegations together in one assistant message and continue useful work while they run (background mode by default).
68
+ - Prefer \`subagent\` for self-contained work and \`workflow\` when many independent pieces need fan-out; their workers run on flash as well.
69
+ - Do not delegate design: subagents execute decisions already made.
70
+ - If a subagent's task grows into design work, pull it back to this agent and re-delegate the narrowed execution.
71
+
72
+ ## Verification
73
+
74
+ - Executor output was produced by \`deepseek-v4-flash\`; planner output by \`deepseek-v4-pro\`. If you need to confirm, check the session log's model metadata.
75
+ - If routing ever looks wrong, the \`model-router\` plugin row in the profile composition is the single place that owns it.`;
76
+ var ROW_ID = "model-router";
77
+ var ModelRouter = class extends Service {
78
+ static inject = ["skills", "systemPrompt"];
79
+ config;
80
+ constructor(ctx, rawConfig = {}) {
81
+ super(ctx, "modelRouter");
82
+ this.config = resolveConfig(rawConfig);
83
+ const harness = ctx;
84
+ harness.on("agent/created", ({ agent }) => {
85
+ const dispose = agent.ctx.on(
86
+ "agent/request",
87
+ async (payload, next) => {
88
+ const resolved = await next();
89
+ const route = routeFor(agent, this.config);
90
+ if (route === void 0) return resolved;
91
+ return { ...resolved, provider: route.provider, model: route.model };
92
+ },
93
+ { prepend: true }
94
+ );
95
+ harness.on("agent/disposed", (disposed) => {
96
+ if (disposed === agent) dispose();
97
+ });
98
+ });
99
+ if (this.config.promptSection) {
100
+ harness.systemPrompt.section({
101
+ name: ROW_ID,
102
+ order: SECTION_ORDER,
103
+ text: SECTION_TEXT.replaceAll("{PLANNER_MODEL}", this.config.planner.model).replaceAll(
104
+ "{EXECUTOR_MODEL}",
105
+ this.config.executor.model
106
+ )
107
+ });
108
+ }
109
+ if (this.config.skill) {
110
+ harness.skills.register({
111
+ name: SKILL_NAME,
112
+ description: SKILL_DESCRIPTION,
113
+ whenToUse: SKILL_WHEN_TO_USE,
114
+ content: SKILL_CONTENT,
115
+ source: "runtime"
116
+ });
117
+ }
118
+ }
119
+ };
120
+ export {
121
+ Config,
122
+ ModelRouter,
123
+ ROW_ID,
124
+ SKILL_CONTENT,
125
+ SKILL_DESCRIPTION,
126
+ SKILL_NAME,
127
+ SKILL_WHEN_TO_USE,
128
+ ModelRouter as default,
129
+ name,
130
+ roleFor,
131
+ routeFor
132
+ };
133
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/policy.ts"],"sourcesContent":["/**\n * dsh-model-router: role-based model routing for the DeepSeek Harness.\n *\n * The planner (the session's root agent) runs on `deepseek-v4-pro`; delegated\n * executor subagents run on `deepseek-v4-flash`. Enforcement is a per-agent\n * `agent/request` rewrite registered when the agent is created, so it applies\n * in every mode (web / headless / tui) and every agent preset, including\n * subagents the delegation tools create.\n *\n * The plugin also publishes:\n * - a system-prompt section stating the planner/executor convention, and\n * - the `pro-flash-routing` skill teaching the agent to plan itself and\n * delegate code execution to flash subagents.\n *\n * @module dsh-model-router\n */\nimport { Context, Service } from \"@deepseek-ai/cordis\";\nimport z from \"@deepseek-ai/schemastery\";\nimport { roleFor, routeFor, type RouterConfig } from \"./policy.js\";\n\n/** Plugin row id; the bundle patch inserts it under this id. */\nconst name = \"model-router\";\n\n/** One provider/model pair, with defaults. */\nconst ModelRouteSchema = z.object({\n provider: z.string().min(1),\n model: z.string().min(1),\n});\n\n/** The plugin's public config, validated at row load. */\nconst Config = z.object({\n planner: ModelRouteSchema.default({\n provider: \"deepseek-official\",\n model: \"deepseek-v4-pro\",\n }),\n executor: ModelRouteSchema.default({\n provider: \"deepseek-official\",\n model: \"deepseek-v4-flash\",\n }),\n promptSection: z.boolean().default(true),\n skill: z.boolean().default(true),\n});\n\n/**\n * Resolve raw row config into the internal shape, failing loud on garbage.\n * Schemastery schemas are callable: invoking validates and applies defaults.\n * @param raw - the row's config object.\n * @returns the validated RouterConfig.\n */\nfunction resolveConfig(raw: unknown): RouterConfig {\n const parsed = Config(raw ?? {});\n return {\n planner: parsed.planner,\n executor: parsed.executor,\n promptSection: parsed.promptSection,\n skill: parsed.skill,\n };\n}\n\n/**\n * Always-on guidance section. Negative order renders before the persona, so\n * the convention is established before the agent's identity line.\n */\nconst SECTION_ORDER = -50;\n\nconst SECTION_TEXT = `Model routing is role-based in this session. You are the planner and run on {PLANNER_MODEL}. Do your own planning, design, review of delegated output, and user-facing synthesis on this agent. Code execution runs on {EXECUTOR_MODEL}: after a plan is approved, delegate implementation work — writing code, running commands, builds, and tests — to subagents, which are automatically routed to {EXECUTOR_MODEL}. Give each subagent a complete, self-contained prompt and prefer background delegation for independent work. Do not hand-write large amounts of code or run long executions on this planner agent; delegate instead.`;\n\nconst SKILL_NAME = \"pro-flash-routing\";\n\nconst SKILL_DESCRIPTION =\n \"Route planning and code execution across models: plan on the pro planner agent, delegate implementation to flash executor subagents.\";\n\nconst SKILL_WHEN_TO_USE = `Use when a task combines planning and implementation: before writing code, after a plan is approved, when delegating execution work, or when the user asks about the pro/flash routing convention.`;\n\nconst SKILL_CONTENT = `# Pro planner / Flash executor routing\n\nThis session routes models by role:\n\n- **Planner (this agent)** — \\`deepseek-v4-pro\\`. Planning, design decisions, reviewing delegated output, and user-facing synthesis happen here.\n- **Executors (every subagent)** — \\`deepseek-v4-flash\\`. Implementation work happens there: writing code, running commands, builds, and tests. The harness forces the model automatically; you do not select it.\n\n## Working rhythm\n\n1. **Plan here.** Explore, decide the approach, and (when plan mode is on) submit the plan with \\`exit_plan_mode\\`. The plan stays on this agent.\n2. **Delegate the execution.** Once a plan is approved, hand each self-contained chunk of implementation to a subagent with a complete prompt: exact files to touch, the change to make, and how to verify. Subagents are automatically routed to \\`deepseek-v4-flash\\`, so keep them execution-focused: give them the decision, not the decision to make.\n3. **Review here.** Read the subagent's result on this agent, verify it yourself (tests, diffs, logs), and iterate with follow-up messages to the same subagent when available.\n4. **Report here.** Summaries, plans, and answers to the user come from this agent.\n\n## Delegation guidelines\n\n- Start independent delegations together in one assistant message and continue useful work while they run (background mode by default).\n- Prefer \\`subagent\\` for self-contained work and \\`workflow\\` when many independent pieces need fan-out; their workers run on flash as well.\n- Do not delegate design: subagents execute decisions already made.\n- If a subagent's task grows into design work, pull it back to this agent and re-delegate the narrowed execution.\n\n## Verification\n\n- Executor output was produced by \\`deepseek-v4-flash\\`; planner output by \\`deepseek-v4-pro\\`. If you need to confirm, check the session log's model metadata.\n- If routing ever looks wrong, the \\`model-router\\` plugin row in the profile composition is the single place that owns it.`;\n\n/** The plugin row id the bundle patch must insert. */\nconst ROW_ID = \"model-router\";\n\n/** Minimal structural view of the live agent object the router reads. */\ninterface AgentLike {\n ctx: AgentScopedContext;\n options?: { subagentDepth?: number };\n session?: { header?: { origin?: string } };\n}\n\n/** The agent-scoped context's waterfall surface the router uses. */\ninterface AgentScopedContext {\n on(\n event: \"agent/request\",\n listener: (\n payload: Record<string, unknown>,\n next: () => Promise<Record<string, unknown>>,\n ) => Promise<Record<string, unknown>>,\n options?: { prepend?: boolean },\n ): () => void;\n}\n\n/** Host-plane surface the router consumes (events, prompt registry, skills). */\ninterface HarnessContext {\n on(\n event: \"agent/created\",\n listener: (payload: { agent: AgentLike }) => void,\n ): () => void;\n on(event: \"agent/disposed\", listener: (agent: unknown) => void): () => void;\n systemPrompt: {\n section(section: { name: string; order: number; text: string }): unknown;\n };\n skills: {\n register(skill: {\n name: string;\n description: string;\n whenToUse?: string;\n content: string;\n source: string;\n }): unknown;\n };\n}\n\n/**\n * Cordis service: per-agent request routing plus the convention surface.\n */\nclass ModelRouter extends Service {\n static inject = [\"skills\", \"systemPrompt\"];\n\n config: RouterConfig;\n\n constructor(ctx: Context, rawConfig: unknown = {}) {\n super(ctx, \"modelRouter\");\n this.config = resolveConfig(rawConfig);\n const harness = ctx as unknown as HarnessContext;\n\n // Every agent that gets created — root sessions, delegation children,\n // workflow workers, ralph rounds — passes through here.\n harness.on(\"agent/created\", ({ agent }) => {\n // `prepend` puts this listener OUTERMOST in the `agent/request`\n // waterfall: the harness's model-selection listener runs inside it, so\n // this rewrite is applied LAST and wins over the session's selected\n // model (which dsh-base defaults to deepseek-v4-flash and the user\n // settings or UI can change).\n const dispose = agent.ctx.on(\n \"agent/request\",\n async (payload, next) => {\n const resolved = await next();\n const route = routeFor(agent, this.config);\n if (route === undefined) return resolved;\n return { ...resolved, provider: route.provider, model: route.model };\n },\n { prepend: true },\n );\n harness.on(\"agent/disposed\", (disposed) => {\n if (disposed === agent) dispose();\n });\n });\n\n if (this.config.promptSection) {\n harness.systemPrompt.section({\n name: ROW_ID,\n order: SECTION_ORDER,\n text: SECTION_TEXT.replaceAll(\"{PLANNER_MODEL}\", this.config.planner.model).replaceAll(\n \"{EXECUTOR_MODEL}\",\n this.config.executor.model,\n ),\n });\n }\n\n if (this.config.skill) {\n harness.skills.register({\n name: SKILL_NAME,\n description: SKILL_DESCRIPTION,\n whenToUse: SKILL_WHEN_TO_USE,\n content: SKILL_CONTENT,\n source: \"runtime\",\n });\n }\n }\n}\n\nexport {\n Config,\n ModelRouter,\n ModelRouter as default,\n name,\n ROW_ID,\n SKILL_CONTENT,\n SKILL_DESCRIPTION,\n SKILL_NAME,\n SKILL_WHEN_TO_USE,\n roleFor,\n routeFor,\n};\nexport type { AgentRole, ModelRoute, RouterConfig } from \"./policy.js\";\n","/**\n * Pure routing policy for dsh-model-router: which model each agent role gets.\n * Kept free of Cordis imports so the policy is trivially unit-testable.\n * @module dsh-model-router/policy\n */\n\n/** One route: a provider/model pair stamped onto an agent request. */\nexport interface ModelRoute {\n provider: string;\n model: string;\n}\n\n/** The two roles the router distinguishes. */\nexport type AgentRole = \"planner\" | \"executor\";\n\n/** Resolved router configuration: one route per role. */\nexport interface RouterConfig {\n planner: ModelRoute;\n executor: ModelRoute;\n promptSection: boolean;\n skill: boolean;\n}\n\n/**\n * Classify an agent as planner or executor.\n *\n * The main (root) agent of a session is the planner. Every agent created as a\n * delegation child — `subagent`, `subagent_fork`, workflow workers, ralph\n * rounds — is an executor. The harness stamps two durable facts on children:\n * `options.subagentDepth` (>= 1) and the session header `origin: \"subagent\"`.\n *\n * @param agent - the live agent (any subset of the runtime shape).\n * @returns the role the agent should be routed as.\n */\nexport function roleFor(agent: unknown): AgentRole {\n const options = (agent as { options?: unknown })?.options;\n const depth = (options as { subagentDepth?: unknown })?.subagentDepth;\n if (typeof depth === \"number\" && depth >= 1) return \"executor\";\n const session = (agent as { session?: unknown })?.session;\n const origin = (session as { header?: unknown })?.header\n ? ((session as { header: { origin?: unknown } }).header.origin)\n : undefined;\n if (origin === \"subagent\") return \"executor\";\n return \"planner\";\n}\n\n/**\n * Resolve the route for one agent.\n * @param agent - the live agent.\n * @param config - the resolved router configuration.\n * @returns the model route to stamp, or `undefined` to leave the request alone.\n */\nexport function routeFor(agent: unknown, config: RouterConfig): ModelRoute | undefined {\n return config[roleFor(agent)];\n}\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "dsh-model-router",
3
+ "description": "DeepSeek Harness plugin: role-based model routing — the planner agent runs on deepseek-v4-pro, delegated executor subagents run on deepseek-v4-flash.",
4
+ "version": "0.1.0",
5
+ "publishConfig": {
6
+ "access": "public"
7
+ },
8
+ "type": "module",
9
+ "main": "lib/index.js",
10
+ "types": "lib/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./lib/index.d.ts",
14
+ "default": "./lib/index.js"
15
+ },
16
+ "./package.json": "./package.json"
17
+ },
18
+ "files": [
19
+ "lib",
20
+ "cordis.patch.yml",
21
+ "skills"
22
+ ],
23
+ "license": "MIT",
24
+ "dsh": {
25
+ "bundle": {
26
+ "patch": "./cordis.patch.yml"
27
+ }
28
+ },
29
+ "scripts": {
30
+ "build": "tsup",
31
+ "prepare": "npm run build",
32
+ "typecheck": "tsc --noEmit",
33
+ "test": "vitest run"
34
+ },
35
+ "peerDependencies": {
36
+ "@deepseek-ai/cordis": "^4.0.1",
37
+ "@deepseek-ai/schemastery": "^3.18.1"
38
+ },
39
+ "devDependencies": {
40
+ "@deepseek-ai/cordis": "^4.0.1",
41
+ "@deepseek-ai/schemastery": "^3.18.1",
42
+ "@types/node": "^22.10.0",
43
+ "tsup": "^8.3.5",
44
+ "typescript": "^5.7.2",
45
+ "vitest": "^2.1.8"
46
+ },
47
+ "keywords": [
48
+ "deepseek",
49
+ "harness",
50
+ "dsh",
51
+ "cordis",
52
+ "model-routing",
53
+ "plugin"
54
+ ]
55
+ }
@@ -0,0 +1,31 @@
1
+ ---
2
+ name: pro-flash-routing
3
+ description: Route planning and code execution across models: plan on the pro planner agent, delegate implementation to flash executor subagents.
4
+ whenToUse: Use when a task combines planning and implementation: before writing code, after a plan is approved, when delegating execution work, or when the user asks about the pro/flash routing convention.
5
+ ---
6
+
7
+ # Pro planner / Flash executor routing
8
+
9
+ This session routes models by role:
10
+
11
+ - **Planner (the root agent)** — `deepseek-v4-pro`. Planning, design decisions, reviewing delegated output, and user-facing synthesis happen here.
12
+ - **Executors (every subagent)** — `deepseek-v4-flash`. Implementation work happens there: writing code, running commands, builds, and tests. The harness forces the model automatically; you do not select it.
13
+
14
+ ## Working rhythm
15
+
16
+ 1. **Plan here.** Explore, decide the approach, and (when plan mode is on) submit the plan with `exit_plan_mode`. The plan stays on this agent.
17
+ 2. **Delegate the execution.** Once a plan is approved, hand each self-contained chunk of implementation to a subagent with a complete prompt: exact files to touch, the change to make, and how to verify. Subagents are automatically routed to `deepseek-v4-flash`, so keep them execution-focused: give them the decision, not the decision to make.
18
+ 3. **Review here.** Read the subagent's result on this agent, verify it yourself (tests, diffs, logs), and iterate with follow-up messages to the same subagent when available.
19
+ 4. **Report here.** Summaries, plans, and answers to the user come from this agent.
20
+
21
+ ## Delegation guidelines
22
+
23
+ - Start independent delegations together in one assistant message and continue useful work while they run (background mode by default).
24
+ - Prefer `subagent` for self-contained work and `workflow` when many independent pieces need fan-out; their workers run on flash as well.
25
+ - Do not delegate design: subagents execute decisions already made.
26
+ - If a subagent's task grows into design work, pull it back to this agent and re-delegate the narrowed execution.
27
+
28
+ ## Verification
29
+
30
+ - Executor output was produced by `deepseek-v4-flash`; planner output by `deepseek-v4-pro`. If you need to confirm, check the session log's model metadata.
31
+ - If routing ever looks wrong, the `model-router` plugin row in the profile composition is the single place that owns it.