DeepSeek Harness Plugin Hub

发布与管理完整 Harness Profiles,发现适合你的插件。

探索

插件目录环境预设文档中心动态

社区

发布插件联系我们报告问题

相关链接

Plugin Hub GitHubDeepSeek Harness 官方项目系统状态隐私说明
© 2026 DeepSeek Harness Plugin HubPowered byPaxTech

独立、非官方社区项目,与 DeepSeek 官方无隶属、授权或背书关系。

Plugin Task Coordinator — DeepSeek Harness 插件(DSH Plugin)
← Plugins
P

dsh-plugin-task-coordinator

Plugin Task Coordinator

面向 DeepSeek Harness 的 Codex-style 跨任务协作:代理监督者可列出、检查、生成、发送消息并引导顶层会话,支持团队工作流、关联 ID、批量生成、交互式调度确认(task_confirm 审批卡片、可复用的任务范围审批)、多选部分调度(task_confirm_select)、现有会话的工作区迁移(task_workspace)、为每个子任务选择模型并实时发现模型路由(task_models)、可通过 GUI 编辑的生成模型默认值(一级“任务编排”设置部分、持久化设置部分)、具备完整可观测性的工作区位置回退机制(cwd 精确匹配、调用方继承、最近祖先规范化、git-w

插件会安装到这里;不确定时保持 web。

npx -y @deepseek-ai/dsh plugin --profile web add github:Kayungko/dsh-plugin-task-coordinator#7031aebb9a4e0d4a5e5df302e80363bb73e96c1c
README兼容性版本

说明

面向 DeepSeek Harness 的 Codex-style 跨任务协作:代理监督者可列出、检查、生成、发送消息并引导顶层会话,支持团队工作流、关联 ID、批量生成、交互式调度确认(task_confirm 审批卡片、可复用的任务范围审批)、多选部分调度(task_confirm_select)、现有会话的工作区迁移(task_workspace)、为每个子任务选择模型并实时发现模型路由(task_models)、可通过 GUI 编辑的生成模型默认值(一级“任务编排”设置部分、持久化设置部分)、具备完整可观测性的工作区位置回退机制(cwd 精确匹配、调用方继承、最近祖先规范化、git-worktree 感知、带修复提示的未分组回执——可通过 workspacePolicy 配置)、遵循宿主语言偏好的本地化用户界面字符串(zh/en)、生成任务的默认结果回报、递归深度治理、工作区继承、持久化生成注册表、/tasks 斜杠命令以及复制会话 ID 的标题按钮。随附任务协作监督者技能。

兼容性与来源证明

Plugin Task Coordinator 以 dsh-plugin-task-coordinator 发布,当前版本为 0.26.5。Plugin Hub 会校验它的 manifest,并保存精确安装来源,便于复现安装结果。

DSH 兼容范围
*
运行环境
web
发布来源
github
Registry 更新时间
2026/9/19

版本

0.26.5stable
2026/9/19
0.26.2stable
2026/9/18
0.26.1stable
2026/9/17
查看其余 16 个版本收起版本
0.26.0stable
2026/9/17
0.25.2stable
2026/9/12
0.25.1stable
2026/9/12
0.25.0stable
2026/9/10
0.24.1stable
2026/9/10
0.24.0stable
2026/9/9
0.23.0stable
2026/9/9
0.21.0stable
2026/9/9
0.18.4stable
2026/9/9
0.18.2stable
2026/9/8
0.18.1stable
2026/9/8
0.17.0stable
2026/9/7
0.16.2stable
2026/9/6
0.16.1stable
2026/9/5
0.16.0stable
2026/9/5
0.14.0stable
2026/9/5

相关插件

正在加载相关插件…

最新版
0.26.5
DSH
*
HMR
重启进程
Tree shaking
未声明可安全裁剪
解包体积
未提供
文件数
未提供
Surface
web
许可证
MIT
发布源
github
GitHub
★ 2
周下载
0
最近提交
2026/9/19
查看源码 ↗
README Badge

点击下方 Badge 复制 Markdown,粘贴到 README 即可。

这是你的 Plugin?认领权益 · 优先安全扫描

验证 package.json 声明的 GitHub 仓库,即可管理这个公开页面。认领后,Hub 会优先安排当前版本的安全扫描,并在通过后公开展示结果。

认领这个 Plugin →
报告问题
DeepSeek Harness Plugin Hub
ProfilesPlugins分类动态文档登录管理 Profiles
ProfilesPlugins分类动态文档登录

相关插件

继续浏览 agents-orchestration 分类下经过校验的插件。

Headless@deepseek-ai/dsh-headlessdsh one-shot bundle:基于 dsh-base 的直接核心 Agent/Session 运行器,不包含 Host、HTTP 或浏览器层Experimental Agent Team Web Profile@deepseek-ai/dsh-experimental-agent-team-web-profile用于 Agent Teams Remote 和 UI 插件的实验性 Web 配置层Subagent Codex@deepseek-ai/dsh-subagent-codex基于官方 app-server 协议的一次性 Codex 子代理提供程序Subagent Claude Code@deepseek-ai/dsh-subagent-claude-code基于官方 Agent SDK 的一次性 Claude Code 子代理提供方

README

task-coordinator

Codex-style cross-task coordination · a supervisor plugin for DeepSeek Harness

What is this · Screenshots · Quick start · Tools · Architecture · Host contract · Changelog · Chinese


What is this

Once installed, you just say what should run in parallel — the task_* tools handle every step of the orchestration:

Split this into three tasks and run them in parallel: A researches the approach,
B builds the prototype, C runs the tests. Summarize for me when they finish.

What happens behind the scenes: you (plain language) → supervisor session → decomposition analysis → an approval card you confirm → batch-spawned task sessions → each task reports its result back when done → the supervisor summarizes. You never watch a single step in between, but the decision points stay in your hands.

  • This is a DSH plugin: any top-level session can discover tasks, read progress, spawn tasks and deliver instructions;
  • Spawned tasks appear in the GUI session list immediately (the same api-session/added event the sidebar consumes);
  • Every cross-task message is stamped with a coordinator source — visible and attributable in the target session's transcript;
  • One prerequisite: DSH Desktop is installed and starts (the plugin never launches the host for you).

📌 Host contract verified on DSH 0.1.2-alpha.1; every capability passed real-host end-to-end testing after restart (criteria in Host contract).

Screenshots

task-coordinator in the DSH Desktop GUI (1/4)task-coordinator in the DSH Desktop GUI (2/4)
task-coordinator in the DSH Desktop GUI (3/4)task-coordinator in the DSH Desktop GUI (4/4)

Live GUI captures — the same four shots are declared in screenshots.json for the dsh-market detail view.

Quick start

Prerequisites

  • DSH Desktop (contract verified on 0.1.2-alpha.1);
  • Node.js ^22.19.0 || >=24 (the host runtime usually satisfies this already);
  • PowerShell (the deploy script is .ps1).

Install (one command)

🛒 Listed on awesome-dsh-plugin (Workflow & Automation) — with the in-app dsh-market plugin browser, search “task-coordinator” and install/upgrade with one click. The git-clone route below is the no-market equivalent.

git clone https://github.com/Kayungko/dsh-plugin-task-coordinator.git
cd dsh-plugin-task-coordinator
pwsh install.ps1 -Source .

The script does three things: copies the plugin into the profile's node_modules/ (no pnpm install, the lockfile stays untouched), registers the dependency + bundle in the profile manifest, and updates .package-map.json — everything is backed up first into backups/<timestamp>/.

Restart DSH Desktop afterwards — any session can then use the eleven tools and the /tasks command.

💡 install.ps1 defaults -Source to $PSScriptRoot/plugin (workspace layout); when running from the plugin repo itself, pass -Source . explicitly. Re-running is safe: file copies are idempotent and manifest registration de-duplicates.

Verify

After the restart, send this to any session:

List the currently visible tasks

It calls task_list and returns the task list (an empty list is a valid answer) — the tools are mounted ✅

Uninstall: pwsh install.ps1 -Source . -Uninstall (also takes effect after restart).

Directing the supervisor (prompting that actually works)

The model only coordinates when it can map your words to the tools. Vague prompts like "you may use the /task plugin whenever you want" are discretionary — sessions tend to default to working solo (field-verified failure mode). Two rules:

  1. Name the tools, use imperative mood. Example: "Split the remaining work with task_spawn_batch into parallel sub-task sessions (give them a team name); collect results with task_wait. Don't do everything in this session."
  2. In /goal mode the coordination mandate must live inside the goal objective — every continuation round re-anchors on that text alone. Recommended objective:

As the supervisor session, take over the remaining development: ① splittable work MUST be dispatched to parallel sub-task sessions via task_spawn_batch (attach a team name) — do not do everything yourself; ② collect results with task_wait and integrate them; ③ sub-task sessions may further parallelize with their own subagents; ④ push to remote main at every milestone.

Loose wordings ("/task plugin", "coordinate things") are recognized too — the bundled skill carries an alias table and the tool descriptions carry trigger context since 0.9.0 — but the imperative template above is the reliable form, especially for goal objectives.

The eleven tools

ToolPurpose
task_listList coordination-visible tasks with stable session ids, status, titles, todo/goal progress; filterable by team; ungrouped: true (0.19.0) lists only sessions belonging to NO workspace — the remediation view for the ungrouped bucket, paired with task_workspace and the registry's expectedWorkspace
task_progressRead one task in depth: live/cold state, queued messages, conversation tail, todos, goal
task_sendDeliver a visible follow-up prompt (mode: queue or steer; reference links an earlier instruction); returns a messageId plus a queueDepth receipt {nextTurn, nextStep} (post-send; one next-turn message is consumed per round, so depth N ≈ N rounds before it is read)
task_spawnCreate + name + kick off a brand-new task (title follows the MMDD|type|topic rule; groupable via team); returns a correlationId; appends the report-back convention by default; optional externalRef (0.25.0) carries a free-form external caller reference for cross-agent correspondence (e.g. a Codex conversation dispatching through the task bridge — trimmed, ≤200 chars, stored durably in the spawn registry and echoed on the receipt / task_list rows / task_progress, never parsed); workspace placement fallback chain (0.19.0): exact-match attachment (0.12.0) → subdirectory attaches to the NEAREST ancestor workspace with the session cwd normalized to the workspace root (default ancestor policy, stated in both the receipt and the kickoff) → git worktrees deliberately stay ungrouped to preserve isolation (strong warning) → every ungrouped landing carries a warning + remediation hint; receipts always report workspace ({id,title}
task_confirmPresent a decomposition/dispatch plan as an interactive approval card and block until the user answers; approval mints a single-use confirmationId
task_confirm_selectPresent the proposed task list as a multi-select card (host's neutral question UI — no amber styling): the user checks which tasks to dispatch (partial dispatch) with an optional custom-feedback row; approval binds the confirmationId to the selected subset and enforces it ( otherwise)

/tasks — the no-model fast lane

Read-only lookups can bypass the model entirely: /tasks (all tasks), /tasks team <name> (one workstream), /tasks <sessionId> (one task's progress, short-id prefixes resolve when unique). The command executes directly in the host — zero tokens, instant answer. Anything that acts (send/spawn/wait/cancel) still goes through the tools.

Copying session ids — one click in the session header

The plugin ships a small web client module (client.js, declared via dsh.client in package.json) that occupies three official slots. ① conversation.session.header.utilities — the same seam the shipped session-log-export package uses: every session header gets a Copy Session ID button (filled pill matching the Session-log button geometry: black-on-white in light mode, white-on-black in dark mode) that copies the session's full sessionId to the clipboard, ready to paste into task_send, task_progress or /tasks <id> on the supervisor side. (The sidebar's per-session context menu is hard-coded in the host and cannot be extended — field-verified — so the header slot is the sanctioned place.) ② settings.section (0.18.1): a first-level Task Orchestration page in the settings left nav (beside General/Models/Plugins/Agent presets, order 25) that edits the default spawn model (see the section above). ③ conversation.view (0.20.0): the Orchestration conversation tab — a live topology overview with the current session as the supervisor (see below).

Orchestration view — a live topology with the current session as supervisor (0.20.0, cards restored in 0.22.0)

The third conversation tab, "Orchestration" (after the native Chat / Trajectory tabs, order 20) is a supervision-only workbench: mounting hides the host composer and its left/right width handles, unmounting restores them; the task canvas scrolls independently and details stay in a non-modal floating right drawer (also on narrow screens), capped to the viewport with its own scrolling body. It anchors on the CURRENT session as the supervisor and renders everything it dispatched in real time — the supervisor node card centered on top, child session cards grouped into team rows below (no-team children fall into the Ungrouped row), and three kinds of directional SVG edges: spawn (solid, downward), message (steer/queue accent, downward, mode-labeled) and report-back (dashed, upward); edges active within the last two minutes carry a dash-flow shimmer (the running-node breathing pulse was retired in 0.26.0); the view spans the actual session panel width capped at 1440px (user ruling 2026-09-18, ratifying the 0912-wave form — the 0.22.3 chat-content-width binding is withdrawn since side-by-side group columns need the width; see CHANGELOG 0.26.0). Clicking a task card opens or switches a non-modal right-side detail drawer; clicking the same card again, pressing Esc, or using the close button collapses it. The drawer shows status, todos, spawn model and parent traffic while the canvas remains full width; navigation happens only via the explicit open-session action. 0.21.x briefly switched to a lane-timeline form; after living with it the user ruled cards more readable for supervision, so 0.22.0 restores the topology and keeps every improvement from the lane era.

  • Node cards: title, short id, model, team chip, status chip (running/idle/completed), todos n/m, goal phase and a relative last-activity time — live states join from the same projection line task_list reads (useSessions), and the supervisor's own state comes from the same source;
  • Click a child to jump straight into that session (the sidebar's navigation primitive sessions.open, degrading to copying the session id);
  • History paging for long sessions (since 0.21.1): the transcript is a finite window, so early spawns can sit outside it — the empty state reports the scanned node count, and when hasMore is set a "Load older records" button pages the store back (sessions.binding(id).session.loadOlder(), the better-display pattern); extraction re-runs automatically after each page;
  • Read-only, zero writes: data comes entirely from the transcript (task_spawn / task_spawn_batch / task_send tool records plus child report-back messages) and the session-list projections — zero host changes; malformed records (window truncation with call: null, unfinished streaming JSON) are skipped silently, never thrown;
  • The empty state guides: a card explains when the session never dispatched a sub-task; opening the tab inside a child session or a plain session shows the same empty state (the view remounts per session and always anchors on the current one).

A nested-supervisor badge (grandchild subtrees) and the global fleet view are planned for phase B; a "focus mode" (a true sequence diagram for one selected session) and an activity-feed drawer are archived research candidates.

Workspace placement: the five-tier fallback chain, fully observable (0.19.0)

Spawned children attach to the caller's workspace by default; the cwd they carry (explicit or inherited) resolves through a five-tier chain, configured by workspacePolicy (default ancestor):

  1. Lexical exact match — the cwd equals a workspace path after normalization (case/separators/trailing separators/. segments, so D:\repo\. == D:\repo) → attach to it (the 0.12.0 upgrade plus the .-segment fix);
  2. Caller inheritance — the caller's workspace membership (including the spawn ancestor chain), then an exact caller.cwd match (unchanged);
  3. Nearest-ancestor upgrade (default tier) — a cwd that is a TRUE subdirectory of a registered workspace attaches to the NEAREST ancestor workspace (nested workspaces pick the closest). The host then derives the session cwd from the workspace ROOT, trading subdirectory isolation for grouping: the receipt carries placement: 'ancestor-normalized' + normalizedFrom, and the kickoff prompt gains one mechanical sentence (zh/en, following the host language) telling the task its cwd was normalized, where its target directory is, and to use explicit paths for file/git operations;
  4. Git-worktree recognition — a cwd whose .git is a FILE (the linked-worktree marker) that missed the tiers above stays ungrouped ON PURPOSE to preserve worktree isolation (the host attaches a session only when its stored cwd equals the workspace path, so joining the main repo's workspace would rewrite the cwd) — with a strong receipt warning and the remediation cost spelled out (task_workspace migrate, which loses the isolation too);
  5. Ungrouped terminal — every other miss: the receipt carries workspace: null + placement: 'ungrouped' + a warning + remediation hints (task_workspace attach/migrate, the task_list({ ungrouped: true }) audit, and team grouping which still works logically).

Observability is unconditional: every spawn receipt reports workspace ({id,title} | null) and the placement enum (exact-match / caller-inherited / ancestor-normalized / ungrouped-worktree / ungrouped); task_spawn_batch items carry the same fields per result; the registry records expectedWorkspace (the directory the caller intended) for later remediation; and task_list({ ungrouped: true }) lists only sessions that belong to no workspace (same single-source resolver as the spawn chain — a subdirectory cwd counts as ungrouped, exactly the remediation candidate this filter exists to surface).

Migration note: pre-0.19 behavior ("an explicit subdirectory cwd stays ungrouped") is now "attach to the nearest ancestor workspace" under the default policy; set workspacePolicy: 'exact' in cordis.yml to restore the exact-only conservative tier. grouping is a reserved future tier and is rejected as an invalid value today.

Fixing & migrating existing sessions (0.12.0 / 0.16.0)

Sessions that landed ungrouped earlier migrate via task_workspace: list the host workspaces, then attach / detach by id or exact path. It calls the live workspace entity — the same attachSession API the host's session.create uses internally — so the session's stored cwd is validated against the workspace path and its conversation is never touched. (The GUI offers no such entry: sidebar dragging calls insertSessionBefore, which only reorders sessions already inside a workspace — field-verified.)

True cross-workspace moves (0.16.0): attach can never move a session whose stored cwd differs from the workspace path — the host validates and refuses, and no host API rewrites an existing session's cwd. action: migrate does the real move: it reads the complete replay-validated log (sessionQuery.readSession, without making the source live), seeds a NEW session born with the target workspace path as its cwd (sessions.create + flush — the same primitive the host's own fork() uses internally; fork() itself deliberately preserves the source cwd/workspace and cannot retarget), attaches the clone, archives the original at the workspace level (workspaceRegistry.archiveSession — a display-layer fold: the old session stays readable and resumable), and carries the plugin registry record (team/depth/parent/title) over to the new id — team filtering and recursion governance survive the move. The task continues under the returned sessionId; message that id, never the old one — the archive does not seal the original, and messaging the archived id would fork the work into two diverging copies. Running sources are refused (migrate-busy — the clone seeds from the persisted log, so settle the turn with task_wait first); a source whose cwd already matches the target is refused with an attach hint; every partial failure reports whether an orphan clone exists and whether the original was NOT archived. Guard note (0.25.1): on host core >=0.1.5 (including 0.1.5-rc.x prereleases) migrate is guard-disabled — the upstream sessions.flush silently drops plugin-created sessions (the clone would "succeed" in-process then vanish on restart, P0) and readSession throws on seeded/forked sessions (an upstream regression unfixed through rc.2, P1); ops.mjs refuses with migrate-disabled (both causes + the attach/detach alternative + the v0.26 sessionPersistence.create(header) write-handle fix pointer, or wait for the host fix) and fail-opens when the probe cannot resolve the core. The 0.1.2 系 clone+archive chain is unchanged — see research/host-upgrade-drift-0.1.2-0.1.5.md face 1.

Per-child model selection (0.13.0) & route discovery (0.14.0)

task_spawn — and every item of task_spawn_batch — accepts an optional provider + model pair (+ reasoningEffort). The route is validated against the host LLM catalog before the session is created (model-unavailable rejects an invalid pair with zero orphans), then installed through the host's sessionController.selectModel between creation and kickoff, so the child's very first turn runs on the requested model; the selection persists as a durable session event and survives restarts. Should installation fail after pre-validation, the spawn reports model-select-failed with the traceable orphan id and never kicks off on the wrong model. Host semantics, disclosed as-is: installing a session-local model also updates the app-wide default model (the GUI picker's "last selection wins" behavior — selectModel is the host's only public entry point), so in a mixed-model batch the last child's route becomes the app default.

Marketplace reality: every user connects different providers/models, so ids are never hardcoded and never guessed — task_models projects the host's live model catalog (the same source the GUI model picker renders) into the exact ids task_spawn accepts, including per-model reasoning efforts and the app-wide default; providers whose catalog listing fails are reported in isolation (failedProviders). A rejected model-unavailable spawn carries an actionable hint too: the error lists what the requested provider actually serves (or the routable providers when the provider itself is unknown). On host builds without modelCatalog(), task_models degrades to catalog-unavailable and the error hints remain the fallback.

Default spawn model — a GUI settings entry (0.18.0, first-level since 0.18.1)

Spawns that omit provider+model no longer fall straight to the host default: the first-level Settings → "Task Orchestration" page (in the settings left nav, beside General/Models/Plugins/Agent presets) configures a default route, making the resolution chain explicit tool args > plugin default > host default.

  • Three cascading selects (Provider → Model → Reasoning effort) whose candidates come from the host's live model catalog (the same source the GUI picker and task_models render) — self-hosted gateway routes (a mana provider, for instance) appear automatically with zero extra configuration; a saved route that later disappears from the catalog still shows as "(unavailable)" and stays editable.
  • Durable storage: the value lives in the host settings service's task-coordinator namespace (the installSection contract, same as the native subagent-model-selection card); GUI edits apply from the very next spawn, no restart.
  • Same validation chain: the default route goes through the identical catalog pre-check (an invalid route is model-unavailable with zero orphans), and the pair rule is enforced at the write boundary (half pairs rejected). A malformed stored layer degrades defensively to "not set" and never breaks the spawn itself.
  • Observable: spawn results echo modelSource (explicit / plugin-default / host-default); task_models carries pluginDefault so one read shows the whole chain.
  • Graceful degradation: hosts without the settings service or the model catalog show a degraded line in the tab; tool behavior falls back to 0.17 semantics, never crashes.
  • Send-queue cap (0.23.0): the page's fourth field, maxQueuePerTask (number, 0–50; 0 = follow the deployment config, default 5) — the per-target task_send queue ceiling that denies with queue-full when full. The limiter reads the cap through a live getter, so a GUI edit applies from the next send check without a restart; hand-edited yaml values above 50 clamp down at consumption.

Localized UI strings (0.15.0)

User-facing strings follow the host's Language preference (Settings → General → Language; the durable locale.preference, zh/en — the same channel the official session-log button uses). The browser-side header button registers dictionaries with the live client locale runtime and re-renders on every language switch; host-side surfaces (dispatch-confirmation cards, the report-back kickoff suffix, /tasks metadata) resolve through i18n.mjs per call, so a switch applies from the next card/kickoff without a restart. An absent or unknown preference keeps the historical Chinese strings — the host side cannot see the "follow the browser" delegation, so the plugin never guesses a language it does not ship. Model-facing surfaces stay as documented: tool descriptions are the English model contract, the SKILL manual is Chinese, and titles follow the MMDD|type|topic convention.

Service seam for bridge plugins (0.24.0)

The plugin's taskCoordinator service now carries the live ops instance in its provide payload ({ config, version, ops } — the exact object the tools use, so limiter / spawn-registry / confirmation state is shared with in-GUI supervisors, never forked), and the service sits in a shared-label isolate realm ('dsh-task-bridge'): a future bridge plugin declaring the same string label resolves it through ctx.get('taskCoordinator'), while everything else still cannot see it. When the plugin is disabled by config the service still exists but omits ops — the documented 503 degrade signal for bridge consumers. Consumers treat ops as read-only: call members, never wrap or replace them. Full seam contract: docs/PROTOCOL.md §17.

Dispatch confirmation (the anti-black-box gate)

Batch dispatches used to be a silent model decision — not anymore:

  1. The supervisor calls task_confirm({ plan }) with the full decomposition plan (markdown); the user gets a plan-review card rendered through the host's ctx.userQuestions seam — the same isomorphic path the official exit_plan_mode uses, so no client-side changes are needed;
  2. Approve → the tool returns a confirmationId bound to the calling session; decline → the user's feedback comes back as the tool result; close the card → confirm-cancelled (the supervisor stops and waits);
  3. task_spawn_batch at or above confirmBatchThreshold (default 2) refuses to run without a valid confirmationId (confirmation-required). The credential is single-use and consumed on success; an all-failed batch keeps it so the user is not asked twice for the same plan.

Multi-select variant (0.10.0): when the tasks are independently droppable, task_confirm_select({ tasks: [{title, scope}] }) renders the list in the host's neutral question UI (multi-select + custom input row, no amber plan-review styling) and the user checks which tasks to dispatch. The minted credential carries the selected subset, and task_spawn_batch rejects any batch title the user did not check (confirmation-mismatch). Present the full plan in chat first — the generic card carries the task list, not the plan body.

Mission-scoped approval (0.11.0): for a long autonomous run (e.g. goal mode), confirm ONCE — task_confirm({ plan, reusable: true }) mints a reusable credential that survives successful batches, so every later batch of the same mission passes the gate with the same confirmationId instead of raising a card per milestone. Single-use stays the default; reusable credentials are still caller-bound and in-process (a host restart clears them); task_confirm_select supports reusable too (subset enforcement applies on every reuse).

Degradation: with no UI connected, task_confirm returns no-question-channel and the bundled skill instructs the supervisor to fall back to a plain-text confirmation in chat. Subagent callers get delegated-caller (a child agent cannot ask a human).

Result report-back

Spawned tasks come with a report-back convention by default (reportBack): the kickoff prompt ends with an instruction to push a result summary (conclusion, output paths, remaining issues) back to the spawning session via task_send when the task finishes — with "write the summary into your final reply" as the fallback when the send fails. The supervisor therefore gets push + task_wait as the pull fallback instead of polling. Pass reportBack: false for fire-and-forget tasks you will read with task_progress anyway. Since 0.17.0 the convention also tells the child to end its turn right after sending (your reply auto-opens a new round on the idle child), and for multi-phase tasks to send a phase report and yield at phases that need your review — the bundled skill pairs this with the phase-review-gate pattern; respond to every phase report, the yielded child hangs until you do.

Recursion governance

Spawned coordinators can spawn further tasks — up to maxSpawnDepth (default 2) generations from the root session. The durable registry records each task's depth and parentSessionId; going deeper fails with spawn-depth-exceeded and the guidance to use subagents for deeper parallelism instead (subagents never consume depth budget).

Delivery semantics (the important part)

Target statetask_send behavior
idleImmediately starts a new round on the target
running + queue (default)Message queues, claimed at the next turn boundary
running + steerMessage queues, claimed at the next step boundary (faster mid-course correction)

Queue-drain mechanics (host-source verified): the next-turn queue is FIFO and exactly one message is consumed per round — the round's first step also absorbs all pending next-step messages — so a message queued at depth N is read after ~N rounds. task_send receipts carry queueDepth {nextTurn, nextStep} (post-send, including your message). steer skips the whole next-turn queue while the target is healthily running, at the cost of extending the current round (several steers land in one batch); on an idle or abort-winding-down target it degrades to next-turn queuing. task_wait returns immediately for cold targets (no live agent) — cold does not mean no pending work.

Three-tier interruption ladder:

TierToolTakes effectSkips the queue?Cost
1task_send queueidle → new round at once; running → first step of the next round (FIFO, one per round)nonone
2task_send steerhealthily running → next step boundary; idle / abort-winding-down → degrades to queueyes (running): ahead of the whole next-turn queueextends the current round; several steers land in one batch
3task_cancelrequests immediate stop (running tool calls finish first); queued messages keptyes (vs the current round)work in flight is lost; the stopped target needs a new message to wake

Rule of thumb: if being one step late wastes a step, steer (stop / course change / conflict warning); otherwise queue (acknowledgments, background, non-urgent handoffs). Inside a single marathon tool call (a full test battery, say) there is no step boundary — even steer cannot get in; only tier 3 applies.

Conclusion: no polling needed — deliver, task_wait for idle, then task_progress for the result. To correct a running task right away use steer; a queue message will not take effect earlier.

⚠️ Delivered ≠ consumed: delivered: true only means the message was accepted into the inbox. On timeout, errors or long silence, reconcile first with task_progress (queued messages + conversation tail), then decide to resend or keep waiting — never blind-resend an uncertain delivery as a new message.

Correlation & traceability

  • task_send returns a messageId; task_spawn returns a correlationId — note down the ones you will need to reference;
  • To correct or continue an earlier instruction, pass reference: <messageId or correlationId> to task_send — the reference is quoted as a visible annotation line in the delivered message, so the target knows exactly which instruction is being amended;
  • Every cross-task message carries the coordinator source, attributable in the target's transcript.

Team workstreams & durable registry

  • Pass team: <workstream name> to task_spawn / task_spawn_batch to group tasks; task_list({ team }) retrieves the whole group later;
  • Grouping is recorded in a durable spawn registry (default <DSH_HOME or ~/.dsh>/task-coordinator/registry.json) together with each spawn's title, prompt excerpt, depth and parentSessionId — it survives host restarts (native session listing cannot answer "which tasks are mine and how do they group");
  • Registry writes are near-atomic (temp file + rename); entries are capped by registryMaxEntries (default 500, oldest pruned first); a corrupt file is preserved as *.corrupt-<timestamp> instead of being silently dropped.

Machine-readable error codes

Failures return { ok: false, code, error } — agents branch on code, never on prose. Two families: guard denials (self-send-denied / subagent-caller-denied / subagent-target-denied / target-not-found / rate-limited / queue-full…) and operation failures (bad-request / target-busy / target-cold / spawn-create-failed / kickoff-rejected / spawn-depth-exceeded / confirmation-required / confirm-cancelled / no-question-channel / delegated-caller / batch-all-failed…). Full table in Host contract §6.

Safety model

  • Self-addressing is always rejected;
  • Targets must be top-level sessions — subagent-owned sessions are fenced;
  • Subagent callers are denied by default (allowSubagentUse to opt in);
  • Batch dispatches above the threshold are impossible without explicit user approval (see above);
  • Recursion depth is capped (maxSpawnDepth) so spawn trees cannot grow unbounded;
  • Per-target rate limit (minSendIntervalMs) and queue-depth limit (maxQueuePerTask) prevent runaway spam;
  • Caller identity is re-derived from the executing agent context on every tool call — never self-reported.

Spawn-title rule (MMDD|type|topic)

task_spawn splits the title responsibilities — the model supplies type|topic; the plugin stamps the date mechanically:

  • The date prefix is stamped from the session creation time in titleTimeZone (default Asia/Shanghai) — never updatedAt, never model-computed;
  • type must be one of titleTypes. The shipped default set is a Chinese octet (literal values in the Chinese README — Feature / Design / Fix / Optimize / Release / Explore / Docs / Research in order); the set is fully customizable, and an all-English octet like ['Feature', 'Design', 'Fix', 'Optimize', 'Release', 'Explore', 'Docs', 'Research'] works out of the box. With the default set, English aliases (fix/bugfix, feature/feat, design, optimize/perf/refactor, release/publish, explore, doc(s)/documentation, research/investigate) are normalized to it case-insensitively; with any set, values also match their own members case-insensitively. Unclear types fall back to titleFallbackType (the shipped default is the Explore slot) instead of guessing;
  • topic is truncated to titleMaxTopicChars (default 16) for sidebar display; with no title, the topic is derived from the kickoff prompt's first line;
  • A stale leading MMDD| is re-stamped from the real creation time; halfwidth | and legacy [team] prefixes are normalized.

Example (English set configured): Fix|reconciliation precision → 0904|Fix|reconciliation precision; with the shipped default set, fix|reconciliation precision resolves to the same title with the Chinese canonical type.

Bundled skill: task-coordination

The plugin ships one skill (skills/task-coordination/SKILL.md) teaching the supervisor when and how to orchestrate the tools: delivery semantics, the three-tier interruption ladder, decomposition criteria, confirmation semantics, fan-out/supervise/handoff patterns, yield-and-wake (goal-mode event loop) and phase-review-gate orchestration patterns, recursion governance, the naming rule, anti-patterns. Loaded on demand — it costs no context until coordination actually happens.

Mounting follows the shipped @openviking/dsh-memory-plugin precedent — an isolated dsh-skill-filesystem provider with providerName: 'task-coordinator', includeDefaultRoots: false, seeing only this plugin's skills/ directory. Consequences: hot-reload on edit, no shadowing of project/user skills, disappears on uninstall. If the provider package is unavailable the mount degrades to a warning — the eleven tools keep working.

Configuration (cordis.yml / patch)

- id: task-coordinator-runtime
  name: 'dsh-plugin-task-coordinator'
  config:
    enabled: true
    allowSubagentUse: false
    includeSubagentsInList: false
    titleTypes: ['Feature', 'Design', 'Fix', 'Optimize', 'Release', 'Explore', 'Docs', 'Research']  # fully customizable; shipped default is the Chinese octet (see Chinese README)
    titleFallbackType: 'Explore'
    titleFallbackTopic: 'New task'  # topic when title and kickoff prompt are both blank
    titleMaxTopicChars: 16
    titleTimeZone: 'Asia/Shanghai'
    registryFile: ''              # empty = <DSH_HOME or ~/.dsh>/task-coordinator/registry.json
    registryMaxEntries: 500
    workspacePolicy: 'ancestor'   # spawn workspace placement: exact = exact-match only (pre-0.19 behavior) | ancestor = subdirectories attach to the nearest ancestor workspace (default); 'grouping' is reserved and rejected
    maxBatchSpawn: 6              # per-call cap for task_spawn_batch
    maxSpawnDepth: 2              # spawn generations allowed below the root session
    confirmBeforeBatch: true      # dispatch confirmation gate
    confirmBatchThreshold: 2      # batch size (>=) at which the gate engages
    maxQueuePerTask: 5
    minSendIntervalMs: 2000
    waitDefaultTimeoutMs: 120000
    waitMaxTimeoutMs: 600000
    excerptChars: 400
    progressTailMessages: 6

Config resolution rejects wrong types instead of guessing: a bad type fails fast with TypeError. See Host contract §5 for every option's semantics.


For developers

Module layering, DI boundaries and the degradation strategy live in docs/ARCHITECTURE.md. Quick reference only here.

Development & tests

node --check *.mjs                      # syntax check
node --test test/smoke.test.mjs         # 105 unit tests (mocked host)
# after installing into a profile (see Quick start):
node verify-installed.mjs               # installed-location integration check: real host packages + mock ctx

Directory

dsh-plugin-task-coordinator/
├── index.mjs           cordis entry · wiring (only layer importing host packages directly)
├── config.mjs          config resolution (pure module)
├── safety.mjs          guards + rate limiter + denial codes (pure module)
├── title.mjs           spawn-title rule (pure module)
├── registry.mjs        durable spawn registry (near-atomic writes, corruption-tolerant)
├── i18n.mjs            zh/en UI-string dictionaries · host-locale resolution
├── ops.mjs             session operations · DI factory
├── tools.mjs           eleven task_* tool registrations
├── commands.mjs        /tasks slash command (direct execution, no model turn)
├── client.js           web client module: copy-session-id header button + Task Orchestration settings page + Orchestration conversation view (dsh.client)
├── skills.mjs          isolated skill mount (dynamic import, fire-and-forget)
├── skills/task-coordination/   supervisor playbook (shipped with the bundle)
├── cordis.patch.yml    isolated plugin-group mount descriptor
├── install.ps1         deploy script (copy-based install + automatic backups)
├── verify-installed.mjs installed-location integration check
├── test/smoke.test.mjs 105 unit tests
└── docs/               ARCHITECTURE.md · PROTOCOL.md

Documentation

  • docs/ARCHITECTURE.md — architecture: why a plugin, module layering, guard layers, degradation strategy
  • docs/PROTOCOL.md — host contract & delivery semantics, field-tested (injection surface, facade signatures, limits, verification records)
  • CHANGELOG.md — release history
  • skills/task-coordination/SKILL.md — the supervisor playbook the model actually reads

License

This plugin is MIT. The @deepseek-ai/* host packages it runs against belong to and are licensed by DeepSeek Harness; they are not covered by this repository's license.

task_spawn_batch
confirmation-mismatch
task_spawn_batchSpawn a whole decomposition plan in one call (tasks: [{title?, prompt}] + one team); requires the confirmationId once the batch reaches the confirmation threshold; one failed item does not abort the rest
task_waitBlock until one task becomes idle (or timeout); multi-target (sessionIds + mode: all/any); cold targets (no live agent) report idle immediately — cold ≠ no pending work
task_cancelCancel the target's active turn, keeping its queued messages (the stopped target needs a new message to wake — cancellation does not auto-drain the queue)
task_workspaceList host workspaces, attach / detach an existing session (fix the ungrouped bucket), or migrate it to a different workspace (0.16.0): clones the full history into a new session born with the target cwd, attaches the clone, workspace-archives the original and returns the new id (workspace-level fold: the old session stays readable and resumable — messaging it would fork the work); refuses running sessions (task_wait first). Goes through the live workspace entity and never touches a session's conversation
task_modelsList the exact model routes this deployment serves — provider/model/reasoning-effort ids from the host's live catalog (the GUI picker's source) plus the app-wide default and the plugin's configured pluginDefault (0.18.0); consult before spawning with provider+model, never guess ids (0.14.0)