Active wait for background subagents in DeepSeek Harness: registers a wait_subagent model tool that blocks until a named background continuable subagent settles, then returns its stop reason and closing message — optional timeout, membership gate rejects unknown ids, event-driven with zero polling. Closes the gap between run_in_background fire-and-return and the async settlement notice; wait for a background subagent without polling list_agents.
Install
# from npm (prebuilt)
dsh plugin --profile web add dsh-wait-subagent
# from GitHub (first run asks for allowBuilds approval — follow the hint, retry)
dsh plugin --profile web add github:john-walks-slow/dsh-wait-subagent
Any plugin you install runs third-party code with your own permissions — it can read your files, use your credentials, and reach the network, and tool approvals don’t sandbox it. GitHub-sourced plugins also run build scripts at install time — pnpm blocks those until you allow them, so an install can stop with ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED or ERR_PNPM_IGNORED_BUILDS; dsh prints the exact key to add under allowBuilds in your profile’s pnpm-workspace.yaml, and the install works on the next run. Allowing a build is a trust decision: only install sources you trust, and pin a commit (github:owner/repo#sha).
README
A DeepSeek Harness (cordis) plugin that registers a wait_subagent tool: block until a background continuable subagent settles, then get its stop reason and closing message in the same call.
It fills a real gap in the subagent workflow: run_in_background returns a subagentId immediately, but the only way to learn the outcome is an async settlement notice — there was no tool to actively wait for it.
Real output
Start a subagent in the background, then wait for it:
wait_subagent({ subagent_id: "session-9f2c47ab-83d1-4e06-b5a9-1c7f2d84e0b3" })
The call blocks while the subagent runs. When it settles, the tool returns:
{
"status": "settled",
"subagent_id": "session-9f2c47ab-83d1-4e06-b5a9-1c7f2d84e0b3",
"stop_reason": "completed",
"closing_message": "Spec written to docs/plan.md; 3 files changed, ready for review."
}
The text the model sees:
Subagent session-9f2c47ab-83d1-4e06-b5a9-1c7f2d84e0b3 settled: completed
Spec written to docs/plan.md; 3 files changed, ready for review.
Other outcomes:
| Situation | Result |
|---|---|
| Subagent already settled before the call | { "status": "settled", "subagent_id": "…", "stop_reason": "unknown" } — "had already settled before this call; its closing message was delivered separately as a settlement notice." |
timeout_ms elapsed |
{ "status": "timeout", "subagent_id": "…" } — "Timed out waiting for subagent …; it may still be running." |
Caller's AbortSignal fired |
{ "status": "cancelled", "subagent_id": "…" } |
| Unknown / foreign subagent id | Rejected with an error: "… is not one of your subagents — pass the id a background dispatch returned (see list_agents)" |
Why
What the subagent system gives you today:
subagentwithrun_in_background: true→ returns asubagentIdat once; work continues asynchronously and a settlement notice arrives later.subagentwithrun_in_background: false→ blocks at start time only.send_message→ fire-and-return, never waits for the answer.list_agents→ a snapshot, not a wait (and explicitly "not to poll").
What's missing: after starting a subagent in the background, there is no tool to block and wait for it to finish. That is exactly what wait_subagent does — use it when your next action depends on the subagent's result, instead of guessing or polling.
How it works
- Registers the
wait_subagenttool on every root agent (same pattern asdsh-proactive). - Membership gate:
ctx.subagents.listChildren(parent.id)— the same projectionlist_agentsreads — covers live AND storage-only children, so unknown ids are rejected with an error instead of being misreported as settled. A known child absent from the live registry has already settled. - The tool listens for the
subagent/endlifecycle event — which fires when a child's activation is disposed — scoped through the plugin context (an ancestor of all agent contexts). - Settlement removes the child from the live registry and dispatches
subagent/endin the same synchronous block (finishDisposal), so attaching the listener first and then re-checking the registry is race-free. - Optional
timeout_msparameter returnsstatus: "timeout"if the subagent does not settle in time. Omit to wait indefinitely — usually the best choice; if set, use a generous value rather than polling with repeated short waits. - Respects the caller's
AbortSignal(returnsstatus: "cancelled").
Tool schema
wait_subagent(subagent_id: string, timeout_ms?: integer)
→ { status: "settled" | "timeout" | "cancelled",
subagent_id: string,
stop_reason?: "completed" | "aborted" | "error" | "max-tokens" | "refusal" | "unknown",
closing_message?: string }
Caveat: don't wait and interrupt in the same step
wait_subagent is concurrency-safe, but interrupt_agent is not — it needs the tool
lane exclusively and queues behind any in-flight call. Issuing wait_subagent and
interrupt_agent in one parallel step serializes them: the wait runs to its timeout
first, and only then does the interrupt land. Call interrupt_agent first, then
wait_subagent in the next step (an already-interrupted child settles quickly).
Install
dsh plugin --profile web add dsh-wait-subagent
Or straight from GitHub (source install — plain ESM JavaScript, no build step, so pnpm ≥ 10 build-script approval is not needed):
dsh plugin --profile web add github:john-walks-slow/dsh-wait-subagent
Restart your DSH instance after installing; the bundled cordis.patch.yml registers the loader entry automatically.
Permissions & compatibility
- What it touches: registers exactly one model-facing Agent tool (
wait_subagent) on every root agent. No web client, no UI changes, no configuration. - Blocking semantics: while waiting, the call holds the calling agent's tool lane until the subagent settles, the optional timeout elapses, or the caller aborts — that is the feature. The wait itself is event-driven (
subagent/endlifecycle event), not polling; CPU cost while blocked is negligible. An omit-timeout_mswait is unbounded by design; set a generoustimeout_mswhen you need a bound. - No side effects: no network requests, no external services, no filesystem writes.
- Dependencies:
@deepseek-ai/dsh-tools0.1.2-rc.1 (aligned with the dsh 0.1.2-rc.1 locked version), Node ≥ 22.5.
Local development
npm install
That's it — lib/ is plain ESM JavaScript; there is no build step and no test suite.
Release a new version
npm run release # bumps the patch version and packs /tmp/dsh-wait-subagent-<newver>.tgz
Then publish the tarball to npm and push the version commit and tag:
git push --follow-tags
Verify with npm view dsh-wait-subagent version.
License
MIT
Community
- Thanks to the LINUX DO community for providing the discussion environment.
Links
More in this category
DayuanJiang/next-ai-draw-io#dsh-plugin★ 36802
Draws draw.io diagrams in DeepSeek Harness through the next-ai-drawio MCP server: the link in the reply opens a live preview in the right sidebar, you can edit the diagram by hand, and each AI change is a version you can restore. Node.js 18+; npx downloads the MCP server on first use.
Tencent/WeKnora#dsh-weknora★ 36302
Four read-only tools over a WeKnora knowledge base: list knowledge bases, hybrid passage search, reassemble one document's chunks in order, and WeKnora's own cited RAG or ReAct-agent answer with a resumable session id.
superdesigndev/treg★ 7042
Tool catalog for agents: search ~2,600 external endpoints (SEO and SERP, backlinks, social, people and company enrichment, ad libraries, scraping) by the task you want done, read each one's parameters and per-call price, then call it with the credential injected server-side. Ships the skill plus an MCP row that stays disabled until TREG_TOKEN is set.
T-Auto/dsh-ops★ 5074
Provides bash, PowerShell 7, and high-performance Rust tools for DSH on Windows to reduce token usage.
sixtysevenlf/dsh-blender-plugin★ 1575
Lets a DSH agent drive a local Blender over a direct TCP channel with 15 tools: viewport frames, custom-angle renders, Python execution, headless and job offload, transaction rollback, and a planning layer of 228 ops across 34 families.
TencentCloudBase/CloudBase-AI-Toolkit#dsh-plugin★ 1155
Tencent CloudBase backend for DeepSeek Harness — scaffold and deploy full-stack apps from chat, render query results as table cards with paging, sorting and CSV export, preview a deployment on its domain, and call the CloudBase MCP toolset (`mcp__cloudbase__*`) with device-code login.
Community comments
Comments are public GitHub Discussions. Loading them connects to GitHub and Giscus; a GitHub account is required to post.