Files

12 KiB

DeepSeek Harness Support

planning-with-files treats DeepSeek Harness (dsh) as a first-class host. Two pieces work together:

  • The skill. dsh discovers SKILL.md files in <project>/.dsh/skills, <project>/.agents/skills, ~/.dsh/skills and ~/.agents/skills, so the workflow instructions load like any other skill, and /planning-with-files typed in the composer invokes it.
  • The native plugin dsh-planning-with-files, a Cordis plugin bundle. It subscribes to dsh's lifecycle events to inject the plan on every prompt, remind after writes, re-inject the plan after compaction and hold a gated turn open until the plan reports complete, and it registers /pwf, /pwf-status and the pwf_init, pwf_status, pwf_check tools. Source: .dsh/packages/dsh-planning-with-files/ in this repository.

The plugin is built and tested against the published @deepseek-ai/dsh 0.1.5-rc.2 and the @deepseek-ai/* 0.1.5-rc.2 libraries it installs (Cordis 4.0.2).

What you get

Lifecycle point Behavior
agent/pre-step Appends the active plan as one plugin-sourced message to the step that carries your prompt: the framed head of task_plan.md (50 lines), the normalized tail of progress.md (20 lines), a pointer to findings.md. Steps that carry only tool results, steering or the plugin's own messages get nothing (the one exception is the first step after a compaction, below), so the plan arrives once per prompt and never twice after a /pwf injection
tools/post-execute Attaches the progress reminder as additional context to a successful write or edit call, and to a str_replace_editor call with a create, str_replace or insert command where a profile mounts that tool (none of the shipped 0.1.5-rc.2 profiles does), while a plan exists
session/event carrying a successful compaction/end Marks the session, and the next step of that agent carries the compaction note (plan pointer, flush instruction, attestation hash) followed by the framed plan as one message, so the continuation resumes at the current phase. Automatic pressure compaction runs inside the compacted turn, so its next step is the continuation; a manual /compact runs idle, so the next prompt is. The plan is read at that step, never queued earlier, so it is never stale
agent/turn-stopping The completion gate in gated mode (below)
Command Behavior
/pwf [--autonomous|--gated] [--template analytics] [plan name] Creates task_plan.md, findings.md and progress.md. A name creates .planning/YYYY-MM-DD-<slug>/ and points .planning/.active_plan at it; --gated or --autonomous writes the v3 markers and attests the plan. The framed plan is queued for the next model request. The handler runs inside dsh without a model round trip and answers with the created files, the plan id and the mode
/pwf-status Plan id, mode, attestation state, current phase, phase counts, and any nested plans below the root
Tool Behavior
pwf_init Arguments name, mode (autonomous or gated), template (default or analytics): the /pwf contract for the model, returns a JSON document
pwf_status JSON summary of the active plan (id, mode, attestation, current phase, phase counts)
pwf_check JSON: whether every phase of the active plan is complete

Commands and tools resolve the plan from the calling agent's session workspace, exactly like the listeners. PLANNING_DISABLED=1 and a broken PWF_PLAN_ROOT pin answer with an explicit error instead of a silent fallback.

Install

1. The skill

npx skills add OthmanAdi/planning-with-files --skill planning-with-files -g

With -g the installer writes ~/.agents/skills/planning-with-files/, which dsh reads with zero configuration. Without -g it writes ./.agents/skills/planning-with-files/ in the current project, which dsh reads as well. dsh honours the frontmatter keys name, description, whenToUse, metadata, disable-model-invocation and user-invocable; the hooks: block is a Claude Code convention that dsh ignores, which is exactly why the plugin exists.

Verify:

ls ~/.agents/skills/planning-with-files/SKILL.md

2. The plugin

dsh plugin --profile web add dsh-planning-with-files

Restart dsh web. dsh plugin forwards to pnpm inside the profile directory ($DSH_HOME/profiles/web, ~/.dsh/profiles/web by default), so pnpm must be on PATH. The package ships built code, so the install asks for no build permission. The same command with --profile headless, --profile sdk or --profile acp adds the plugin to those profiles; restart the matching app afterwards.

Verify without booting:

dsh --profile web --dump-config

The dump prints a # == dsh-planning-with-files layer with the planning-with-files row.

Configuration is optional. To keep injection and switch the completion gate off, override the row's config in the profile's cordis.patch.yml ($DSH_HOME/profiles/web/cordis.patch.yml). A patch replaces the row's whole config value, so restate both keys:

- id: planning-with-files
  config:
    enabled: true
    gate: false

3. First run

Start dsh web in a project and type:

/pwf My task --gated

The command answers with the created files, the plan id (YYYY-MM-DD-my-task) and the mode; the next message you send carries the framed plan. /pwf-status reports the plan. Asking the model to call pwf_status exercises the tool path.

How plan selection works

Every event resolves the plan again from the session's working directory (the cwd on the session header), in this order:

  1. PLAN_ID=<slug> in the dsh process environment binds one plan. A slug that names no plan ends resolution: nothing is injected, and neither the pointer nor the root plan is used instead.
  2. Without PLAN_ID, two or more named plans under .planning/ are ambiguous: the first prompt gets the one-line Multiple plans are available notice and nothing is injected until PLAN_ID is set. A shared pointer or a directory timestamp cannot tell which plan this session means.
  3. With one named plan, .planning/.active_plan (BOM tolerant) or the plan directory itself selects it.
  4. The legacy task_plan.md in the project root.

PWF_PLAN_ROOT=<absolute path> pins the project root; a pin that does not resolve to a real directory fails closed. When a direct child project carries its own live plan, a cwd guess is ambiguous: the plugin injects a one-line notice instead of a plan until you pin the session with PWF_PLAN_ROOT or PLAN_ID. PLANNING_DISABLED=1 silences every listener, command and tool.

Autonomous and gated plans inject only when .attestation (slug plan) or .plan-attestation (root plan) matches the SHA-256 of task_plan.md; a tampered or unattested v3 plan is refused with a context blocked line. /pwf --gated and pwf_init with mode: gated attest the plan they create. After editing the plan, re-attest it (sh scripts/attest-plan.sh from the installed skill) or recreate it.

The environment variables are read from the dsh process, because the plugin runs inside it: set them before starting dsh web, not in a shell inside the session.

The completion gate

dsh cannot refuse to end a turn, but agent/turn-stopping is awaited before the turn closes, and a listener that steers the agent makes the machine run another step. The gate uses that: when a gated plan still has an in_progress phase, the plugin steers the agent with the same reason the Claude Code Stop gate prints, and the agent continues. Decision table, shared with check-complete.sh --gate and the OpenCode plugin:

  1. <plan-dir>/.mode contains the gate token (/pwf --gated, pwf_init with mode: gated, or init-session.sh --gated).
  2. An in_progress phase exists, counted as the per-field maximum of **Status:** in_progress lines and inline [in_progress] markers.
  3. The block counter <plan-dir>/.stop_blocks is below PWF_GATE_CAP (default 20).
  4. The ledger (<plan-dir>/ledger-*.jsonl) advanced since the previous block; a stall releases the turn.

Each block increments the counter and records the ledger size, so the shell gate and the dsh gate share one state. Subagent children are never held: the gate applies to the delegating agent only. This is Tier 2 in the host capability tiers (follow-up inject): the continuation is a new step, not a refusal, and a user who wants the turn to stop can edit the plan, clear the .mode file, set PLANNING_DISABLED=1, or set gate: false on the row.

What is not covered

  • No per-tool-call recitation. dsh's tools/pre-execute decision carries no context field, so the plan reaches the model once per prompt through agent/pre-step. This matches the autonomous-mode injection shape on Claude Code.
  • No session catchup adapter yet. Automatic recovery reads the planning files on disk; session-catchup.py does not open dsh's session store under ~/.dsh/sessions/.
  • hooks: frontmatter. dsh ignores the hooks: block in SKILL.md; the plugin is the replacement.
  • dsh's own /plan. That command is dsh plan mode and stays untouched; the plugin registers only /pwf and /pwf-status.
  • Prompts from other plugins. Only a step that carries a user-sourced message gets the plan. A turn driven by another plugin's message (a /goal round, for example) is a continuation and gets nothing.
  • agent/session-start sources. The published 0.1.5-rc.2 declares compact as a possible source but every dispatch site announces startup or resume, and the 0.1.6 alpha line does the same after folding the field into agent/created. The plugin therefore keys the compaction re-injection off the durable compaction/end session event, which both lines write, and listens to agent/session-start for nothing.

Windows notes

  • Plan paths, .planning/.active_plan and the attestation files use Windows paths; a UTF-8 BOM in .active_plan (a PowerShell redirect) is tolerated.
  • PWF_PLAN_ROOT must be an absolute path with a drive letter (C:\projects\app); a rootless \projects\app and a UNC path are refused.
  • dsh plugin needs pnpm on PATH (npm install -g pnpm).
  • Set the environment variables for the dsh process, for example $env:PLAN_ID = "2026-09-17-my-task"; dsh web in PowerShell.

Troubleshooting

  • dsh plugin add printed missing-peer warnings. The profile installs no peers on purpose (autoInstallPeers is off); the @deepseek-ai/* packages resolve from the dsh installation through $DSH_HOME/profiles/node_modules. The warnings are harmless.
  • The plugin is missing from --dump-config. dsh plugin --profile web why dsh-planning-with-files shows whether pnpm installed it; re-run the add. A package without dsh.bundle in its package.json installs as a plain dependency and activates no layer; the published package declares it.
  • Nothing is injected. Check PLANNING_DISABLED, then /pwf-status: no plan, a context blocked line (attestation), or the ambiguity notice each name their fix. A planning-with-files: <point> skipped after an error warning in the dsh log means the plugin contained an error and the turn proceeded without planning context.
  • Ambiguous plans. A live plan in a direct child project competes with the cwd plan; pin the session with PWF_PLAN_ROOT=<absolute path> or PLAN_ID=<slug>.
  • The gate keeps steering. The cap (PWF_GATE_CAP, default 20) and the ledger stall rule bound it; mark the phase complete in task_plan.md, remove .mode, or set gate: false on the row.

From source (contributors)

git clone https://github.com/OthmanAdi/planning-with-files.git
cd planning-with-files/.dsh/packages/dsh-planning-with-files
npm ci && npm run build && npm test

To load the checkout into a profile, build it first (a linked checkout has no dist/ until you do) and link it by absolute path:

dsh plugin --profile web add /absolute/path/to/planning-with-files/.dsh/packages/dsh-planning-with-files

src/core.ts is a byte-identical copy of the OpenCode plugin's core and tests/test_dsh_plugin_core_parity.py locks the two together: a fix goes into the OpenCode copy and is copied over.

Learn More