Preface¶
Anyone writing a DSH profile will eventually encounter a specific problem: The parent agent needs to hand a task to a child agent, but the child agent runs in an independent CLI process, not an in-process teammate, nor a local child that can continue a conversation. At this point, using a general delegation tool only yields started subagent <id>. The model doesn’t know it has started running and can’t wait for any progress, so it repeatedly asks wait_agent / list_agents. However, these two tools only track in-process teammates and local children that can continue, and they will forever answer “no subagent running” for external one-shot child agents.
The bundle introduced below, lloydzhou/dsh-subagent-codebuddy, is a profile bundle designed for this scenario: It delegates each time to the local CodeBuddy CLI (codebuddy --acp), the model is pinned by configuration, and the tool descriptions and result copy explicitly tell the model “the child agent is already running, don’t wait for it.”
What is this¶
One-sentence positioning: An independent DeepSeek Harness profile bundle that provides a one-shot (one-shot) child agent provider. Every subtask runs in the local CodeBuddy CLI as an ACP session, and the child model is pinned by configuration (default deepseek-v4.1-flash, passed via --model <id>).
- Maintainer: lloydzhou
- Package name:
dsh-subagent-codebuddy, published with the same name on npm - Version: 0.2.1-rc.5 (see package.json)
- License: MIT
- Language: TypeScript
- Category: Workflow
- Source attribution: The community directory page is on skillhub.cn, an independent community site; the plugin itself is an open-source repo. The directory site and DeepSeek / Huansuan have no official affiliation.
- Stars: 0 (as of writing time, from GitHub repo page)
It solves three interconnected problems: Delegation must be able to truly initiate, results must be able to return to the parent session when the child agent settles, and the model should not poll a running process that can never be found due to vague results.
Runtime Behavior: One-shot Delegation and Result Return¶
The provider is written on the public subagent interface exposed by the harness. Specifically, it uses three helpers from @deepseek-ai/dsh-subagent: NO_START_CAPABILITIES, settleRunResult, and subprocessRunHandle. It does not have prepareContinuable, so there is no inbox on either side: The CodeBuddy child agent is an independent CLI process, has no dsh tools, and cannot callback the parent session.
The result return method is therefore very limited: The child agent reports only once when it settles. The harness injects a Background subagent <id> finished notice, carrying the child agent’s closing message. This notice triggers the next round of the parent agent. There is no progress channel during the run—the README emphasizes this is a backend feature, not a plugin bug.
As a comparison, the README explains that notices like Team message from <child>: … belong to local child agents that are continuable. They come with the dsh send_message tool and the parent session’s inbox. The CodeBuddy child agent does not belong to this category, so such mid-run messages do not appear.
Therefore, the tool’s job becomes: Prevent the parent agent from waiting in vain. Its result copy explains that polling is ineffective and the parent agent should continue doing other things or end the current round, allowing the settle notice to arrive at the earliest step boundary.
Two Tools on the Model Side¶
The two tools mounted by the bundle both come from the bundle’s own ./tool entry point, not the general @deepseek-ai/dsh-tool-subagent:
subagent_codebuddy: Starts a run oncecodebuddy_steer({ childId, message }): Appends messages to a run that is still running
Both tools follow the same runtime path ctx.subagents.startActivation({ delivery: 'parent' }); the difference is in the copy. The general tool’s started subagent <id> has no wording knob, and the model has no reason to stop looking for this run; this bundle’s description and results explicitly state that the child agent is already running, no tool can observe it, and results will arrive automatically.
codebuddy_steer wraps the CLI’s ACP steer extension, which is the only channel to get messages while running. The README provides a comparison table:
second session/prompt → CLI treats it as a competing round, finishes first round in ~33ms, output is empty
--input-format stream-json → Starts a new session, the currently running one cannot see it
session/steer → {steered: true}, the message is delivered at the next step boundary of the child agent,
ongoing tool calls are not interrupted
childId is the id in the activation notice. The provider publishes a live handle in src/steer.ts when session/new returns, and revokes it on run settle, so already finished ids are rejected, and the tool switches to listing live ids instead of guessing. When the child agent returns {steered:false}, it is reported as rejected with a reason and does not retry; the common reason is that the child agent is between rounds.
What the Chat Card Shows¶
Each delegation generates a card in the transcript (conversation.chat.node, kind is codebuddy-run), anchored to its own tool/call event. The card folds three things: handover result, settle notice, and messages sent mid-run. Since the external child agent only produces these three moments, the range of what the card can honestly express is limited to these:
| Status | Trigger Source | Card Content |
|---|---|---|
| Running | tool/call |
Timer starts from call |
| Running in background | tool/result (activation notice) |
Timer continues; body points to runtime and logs, filling with child agent actions |
| Completed | settle finished |
Closing statement, attached under task and timeline, CLI’s cost line mentioned in header |
| Stopped | settle was stopped |
The notice is preserved in full |
| Rejected | settle declined the task |
Same as above |
| Failed | settle failed before it finished / ended abnormally, or runtime throws error |
Diagnostic info, plus all - [done] … steps |
The principle is “one run, one card.” codebuddy_steer calls in the transcript are also normal tool calls, matching /codebuddy/. If only the name is looked at, it might open a separate card that never settles, stopping at “Running in background” next to the run that just sent a message. So the definition is based on what the call does: arguments containing childId are “speaking to a run” rather than “starting a run,” so they fold into the card of the child they are speaking to (displayed as ↪ Interjection … in the body). If the current page has never seen that card, it folds into an empty one. Its own result is ignored in any case—the run status only changes when the child agent settles. Normal tool rows report the call normally.
The card body is a record, not a replacement: read in order of when the run happens—the child’s task (full prompt, header only puts the caller’s 3-5 word tag, body folds four lines, expandable locally), mid-run messages, things the child did, and finally how it ended. Settle is an append of the closing block, not overwriting existing content. To still hold up after remounting (reconnecting, re-rendering transcript from session log), there is a small cache per run in src/client/live.ts: polling stops after settle; without it, history is lost when re-rendering completed cards.
Two consequences that need to be stated directly:
- The timer measures waiting, not life. Between the two events, the card hears nothing from the transcript; the browser cannot read the run’s beacon; so the header only counts and explicitly states this, hover text points to
codebuddy-runs, where the question can indeed be answered. The actions of the child agent that the card can actually see depend on whether the host responds (see next section). - Child agents that die with the host will forever stop at “Running in background,” because there are no events to fold;
codebuddy-runscalls this lost connection. The case where the host is alive but the child dies is another scenario: The ACP pipe closes, the run settles witherror, and the card turns to “Failed”; this scenario is fixed bycrashintests/mock-acp-child.mjs.
Real-time Progress: The Only Channel Outside the Transcript¶
Cards that only fold transcript events can only show two moments during a ten-minute run. The child agent’s actions are not in that transcript—they are ACP session/update notifications that arrive at the host process, and the provider already mirrors them into the run logs. src/progress.ts keeps the most recent 60 per run in memory, keyed by the run id used at runtime (i.e., child id), and src/progress-route.ts serves it on the host’s own listener:
GET /subagent-codebuddy/progress?ids=<childId>[,<childId>…]
→ { runs: { <childId>: { steps, updatedAt, endedAt? } } }
The card polls this path during the run (src/client/live.ts, every 1.5s) and stops after settle or three consecutive failures. The README lists four design decisions, one of which is using a relative URL instead of a port, because the web UI is served by the same listener. The action lines the card can see appear as the host responds; “Last action 12s ago” gets older over time; the N steps count in the folded header changes; without this feed, it falls back to pure timing.
This package also comes with a codebuddy-runs command line entry (bin in package.json), for manually viewing run status.
Installation and Enablement¶
First, let’s clarify one thing: The README I have does not give a one-click install command that can be copied directly, so I won’t assemble one here dsh plugin add .... Looking at package.json, this package declares a bundle structure:
{
"dsh": {
"bundle": { "patch": "./cordis.patch.yml" },
"client": {
"inject": ["@deepseek-ai/dsh-client-ui-conversation"],
"platform": "web"
}
},
"engines": { "node": ">=20", "dsh": ">=0.2.1-rc.3" },
"bin": { "codebuddy-runs": "./bin/codebuddy-runs.mjs" },
"license": "MIT"
}
In other words: After installing this package, the harness reads dsh.bundle.patch pointing to cordis.patch.yml, where it inserts two lines—the provider line and the tool line mounting this bundle’s ./tool entry. The original text of cordis.patch.yml is as follows:
- insert:
- id: subagent-codebuddy
name: 'dsh-subagent-codebuddy'
config:
permission: allow
startupTimeoutMs: 60000
envFile: ~/.dsh/secrets/codebuddy-api-key
env:
CODEBUDDY_INTERNET_ENVIRONMENT: internal
- id: tool-subagent-codebuddy
name: 'dsh-subagent-codebuddy/tool'
config:
provider: codebuddy
toolName: subagent_codebuddy
maxDepth: provider-managed
A few configuration points need to be explained, they all come from comments in the patch’s original text:
permission: allowallows the child agent’s tool prompt to be automatically approved, real tasks can proceed; change torejectwhen strict read-only children are needed.envFile: ~/.dsh/secrets/codebuddy-api-keyspecifies the file holding the API key; the run consumes the subscription quota of the logged-in account, or thisenvFileprovided key.CODEBUDDY_INTERNET_ENVIRONMENT: internalexplicitly specifies the CodeBuddy site used when verifying the key. The domestic site (copilot.tencent.com) and the international site (codebuddy.ai) do not accept each other’s keys, and the CLI selects the site based on the product configuration cached on the machine; machines that have never installed the desktop client will fall back to the international site, causing a completely valid key to report 401. Explicitly writing the name here bypasses this dependency.maxDepth: provider-managedis a required field and must be explicitly written with this value. If the key is omitted, the harness will fall back to its own default numeric depth, and the CodeBuddy provider does not advertise adepthLimitcapability, so it cannot enforce it. This line will fail to activate directly with an errorprovider "codebuddy" cannot enforce maxDepth .... For external child agents,provider-managedis the only valid value.
Before enabling, you need to satisfy peer dependencies (from peerDependencies): @deepseek-ai/cordis ~4.0.5, and @deepseek-ai/dsh-session, @deepseek-ai/dsh-subagent, @deepseek-ai/dsh-subprocess, @deepseek-ai/dsh-timeout are all ^0.2.1-rc.3; Node requires >=20.
There is also a placement constraint written in the cordis.patch.yml comments: both lines must be placed at the top level of the bundle. If you mount the tool into an agent preset (e.g., preset-cordis), it is tied to that preset, and any profile without that exact preset line cannot mount the tool, so the provider has no path for the model to reach it.
Use Cases and Notes¶
Who is it for: Developers who are already writing profiles on DSH and want to put subtasks into the local CodeBuddy CLI; people who need external one-shot delegation and can accept the backend feature that “no mid-run progress, results only come back once at settle.” If you need the child agent to send and receive messages mid-run bidirectionally, this plugin does not satisfy it; it only provides one-way steer from parent to child.
Points to note:
- The plugin runs with the permissions of the current DSH process, will read the key file pointed to by
envFile, and will spawn the localcodebuddy --acpsubprocess. Read the source code and LICENSE (MIT) before installing to confirm you can accept this permission surface. - Expenses go through the CodeBuddy account’s subscription quota or the key provided by
envFile, not the dsh side’s quota. - There is no mid-run progress channel, do not expect the model to see anything during the waiting period on the parameters. The tool has already tried its best with the copy to prevent the parent agent from waiting in vain, but before settle, the parent session cannot get the child agent’s actions unless the host provides the progress feed from the previous section.
- When the child agent dies with the host, it will permanently stop at “Running in background,” because there are no events to fold; this is a design boundary, not a fixable bug.
- Stars are 0, the repo was created in the last few days, and in the ecosystem it is a very new small project. It is worth checking the repo’s activity and dependency versions yourself before adopting.
- The community directory site is an independent site, the plugin is not an official release by DeepSeek / Huansuan.
Conclusion¶
The value of this package is not in doing something others can’t, but in clarifying a boundary case: External one-shot child agents have no mid-run progress; the four layers of tool copy, steer channel, chat card, and progress feed are all designed around the goal of “preventing the parent agent from waiting in vain and letting results arrive honestly.” If your profile needs to delegate to the local CodeBuddy CLI, it is an implementation that can be directly put into the bundle.
- Directory page: https://www.skillhub.cn/plugins/lloydzhou/dsh-subagent-codebuddy
- GitHub: https://github.com/lloydzhou/dsh-subagent-codebuddy