Back to home@tkdfs

dsh-ptc-auto-preset

DSH preset: auto-assigns model + reasoning effort per work phase in PTC sessions — plan→pro+max, implement→pro+high, mechanical→flash+high.

Stars
0
Language
JavaScript
Created
Aug 14, 2026
Updated
Aug 14, 2026

Introduction

PTC Auto-Assign (ptc-auto preset)

A resident DSH agent preset: in PTC (Code Mode) sessions, it automatically assigns the model and reasoning effort for every request according to the current work phase.

中文文档: README.zh-CN.md

Rules (from PROJECT.md §7 recommended config)

PhaseTriggerModelReasoning effort
Plan/architecturePlan mode on, or the message contains 设计/架构/规划/方案/计划/接口/重构 (or English design/architecture/plan/refactor, etc.)deepseek-v4-promax
Implement/fix testsDefault (no keyword hit)deepseek-v4-prohigh
Mechanical editsMessage contains 文案/机械性/补全/整理/重命名/格式化/迁移/注释/标注 (or English rename/formatting, etc.)deepseek-v4-flashhigh

All keywords and gears are configurable in the ptc-assign row's config inside agent.cordis.yml — no code changes needed.

Install

Copy this whole directory to:

${DSH_HOME:-$HOME/.dsh}/.agent-presets/ptc-auto/

(The preset id = the directory name ptc-auto; renaming the directory = changing the id.) After copying, start a new session and pick 「PTC 模式(自动分配)」 in the preset picker (recommended: "set as default"). Verify with /ptc status.

You can also let an AI deploy it — paste this to your AI assistant:

Clone https://github.com/tkdfs/dsh-ptc-auto-preset into ${DSH_HOME:-$HOME/.dsh}/.agent-presets/ptc-auto/, keeping the directory name ptc-auto.

The clone target is all it takes; the directory name becomes the preset id. Then start a new session and pick the preset in the picker.

Usage

  • Auto-effective: every message shifts gears by phase before it is sent; the first message already applies.
  • /ptc status: show the current gear, the classification basis and diagnostics (claims/preSteps/requestSteps/trace/lastError).
  • /ptc auto|plan|implement|mechanical|off: manually pin a gear or turn auto-assign off.
  • Model-side tool ptc_assign: invoked by the model when the user asks in natural language to adjust the gear.
  • Manual model-selection protection: after manually picking a model in the UI, the plugin detects the deviation and auto-pauses (pins); /ptc auto resumes.
  • Known display lag: see "Known issue: UI picker display lag" below.

Implementation mechanism (must read for maintainers)

  1. In Web sessions, the model is decided by the "session selection layer" (api-proxy's installModelSelection), which registers before the preset mounts and wraps the outermost agent/request waterfall — any waterfall rewrite registered later gets overridden by it.
  2. Therefore this plugin does not compete on the waterfall; instead, at agent/inbox/claimed (when a message is claimed, before the system prompt is assembled), it writes the target config into the session's persisted request/header event — which is exactly the source the session selection layer reads. After writing, the request immediately executes with the assigned config, keeping the UI picker, session log, and restart recovery consistent automatically.
  3. agent/pre-step is kept as a backup write point (after assembly; on Web it usually no-ops due to skip-same, but it still works on runtimes without a session selection layer).

Maintenance conventions (pitfalls we hit; must follow)

  1. When changing plugin code you must rename the file: ptc-assign.vN.js, and update that row's name: in agent.cordis.yml. Why: the resident agent only detects a new generation by the mtime of agent.cordis.yml, and Node ESM caches plugin modules by URL — editing a file in place leaves the new generation loading old code (cost us several rounds of debugging).
  2. For rule gears, only edit the config in agent.cordis.yml; after touching the yml, start a new session and the new generation applies.
  3. Verify: agentPresets.standingKeyFor('ptc-auto') mount check (mounted OK), then a real-session round (or a controlled mock agent).

Debug history (2026-08-14)

SymptomRoot causeFix
Auto-assign never took effect; every request used the session default gearThe session selection layer overrode the waterfall rewriteRewrite the persisted request/header
New generation didn't load new plugin codeESM caches by URL + generation detection only looks at agent.cordis.ymlVersioned filenames + touch the yml
pre-step header write was one step too lateSystem prompt assembly happens before pre-stepMove the main write point to inbox/claimed
Misclassification ("好的" judged as plan gear)Injected messages (skill catalog, runtime snapshot, etc.) were treated as the last user messageOnly accept human messages with source.kind === 'user'

Verification result (reconciled against real session logs): the synthesized header (sys:false) is written before the loop's own request header; all three test segments hit — implement→pro+high, mechanical→flash+high, plan→pro+max.

Known issue: UI picker display lag

Symptom: when the first message (or the first after each gear shift) is sent, the model picker in the UI still shows the session's old gear, making it look like "auto-assign didn't take effect" — yet the reply's actual reasoning depth / log records are the new gear.

Verdict: purely a display issue, not an execution issue. Reconciled message-by-message against session logs: the synthesized request header is written before the loop's own request header, and requests actually execute with the assigned gear (evidence: the first plan-mode reply is already a full max-gear deep-thinking stream; request/header's provider/model/reasoningEffort are all correct).

Root cause: the UI model picker is pull-based; it re-fetches the session model from the server only when:

  1. opening the picker menu;
  2. manually selecting a model;
  3. reconnecting after a disconnect;
  4. the adapter catalog (llm/adapters-updated) or the settings document (settings/document-updated) changes.

This plugin is an external writer (it directly mutates the session's persisted request/header), and the product has no "request header changed → push a picker refresh" channel, so the picker stays at the last pulled state.

Impact & workaround:

  • Impact: display layer only; it does not affect actual model/reasoning-effort execution, nor session logs/restart recovery.
  • Workaround: to check the real gear — open the picker menu once (triggers a re-pull) or just /ptc status.

Alternative fix (not adopted): build a resident client-side sync plugin (subscribe to session events → re-pull sessions.models) so the picker refreshes in real time on every message. Not adopted because: it requires building a client plugin package, bundling it, integrating into the deployment and maintaining it across upgrades — cost is disproportionate to the benefit; the behavior itself is correct, and the display just lags one "open the menu" gesture.

File list

  • preset.yml — preset display metadata (name/description)
  • agent.cordis.yml — composition file: full PTC mode (code preset) capabilities + the ptc-assign row
  • plugins/ptc-assign.v4.js — plugin implementation: claimed/pre-step writes, the /ptc command, the ptc_assign tool, diagnostic state