Skip to content
dsh.fish
Agent preset

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.

Source
tkdfs
License
MIT
Updated
Updated 2 days ago

Readme

# 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](README.zh-CN.md)

## Rules (from PROJECT.md §7 recommended config)

| Phase | Trigger | Model | Reasoning effort |
|---|---|---|---|
| Plan/architecture | Plan mode on, or the message contains `设计/架构/规划/方案/计划/接口/重构` (or English design/architecture/plan/refactor, etc.) | `deepseek-v4-pro` | `max` |
| Implement/fix tests | Default (no keyword hit) | `deepseek-v4-pro` | `high` |
| Mechanical edits | Message contains `文案/机械性/补全/整理/重命名/格式化/迁移/注释/标注` (or English rename/formatting, etc.) | `deepseek-v4-flash` | `high` |

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)

| Symptom | Root cause | Fix |
|---|---|---|
| Auto-assign never took effect; every request used the session default gear | The session selection layer overrode the waterfall rewrite | Rewrite the persisted request/header |
| New generation didn't load new plugin code | ESM caches by URL + generation detection only looks at agent.cordis.yml | Versioned filenames + touch the yml |
| pre-step header write was one step too late | System prompt assembly happens before pre-step | Move 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 message | Only 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

Install

# Copy the composition to $DSH_HOME/.agent-presets/dsh-ptc-auto-preset/agent.cordis.yml

Profile: web

Source