DeepSeek Harness plan mode submits while subagents still run
Nothing is racing in DeepSeek Harness — the check is simply absent. None of exit_plan_mode's four preconditions involves delegation, and the default policy text never mentions in-flight subagents. The model can only poll; when polling returns running and submission is still allowed, the plan goes through (source).
One real DeepSeek Harness plan mode trace: running at 138, submitted at 139
Bottom line: the interesting part is not disobedience — the model did the only thing the current tool surface allows: poll, see running, then follow the policy rule that the response must end with a single tool call.
- In plan mode, start two read-only research subagents from one assistant message; they run in the background by default. Expected: both return ids and the parent turn continues.
- Let the parent keep working and watch
list_agents. Expected: repeated[running]; the trace in the report polled 8 times. - Before either report lands, let the parent submit a plan. Expected:
exit_plan_modesucceeds and enters review, with no "delegations still running" hint anywhere in the UI. - Read the parent's reasoning. Expected: it says explicitly that those findings will be folded in during implementation — the model knows its inputs are incomplete.
- After the user dismisses, wait for the reports and the parent immediately submits v2, which is approved. Expected: the v1 review was pure noise for the user.
- Walk the session events and compare
tool/callagainstplan/modetimestamps. Expected: the submission precedes the end of the subagent turn, not follows it.
Subagent lifecycle and diagnostics are a separate class of problem; see subagent diagnostics and lifecycle.
Why adding a prompt cannot stop it
Bottom line: prompts and execution are separate paths — policy text can say "wait first", but whether a tool call is accepted is decided in code, with no enforcement link between them.
- Read the default
plan:policytext: it lists what a complete plan must cover (goals, change grouping, interfaces and data flow, boundaries, failure modes, tests, acceptance) and says nothing about delegation. - Read the tool description:
subagentsays it returns an id immediately and notifies you when it finishes, so the model's only waiting primitive is "end the turn and wait for the notification". - The same policy also requires that, when submitting,
exit_plan_modebe the only and last tool call in that response — which makes "list once, then submit" look like a reasonable path. - Conclusion: changing wording alone still allows submission after a
[running]result; only a failing call keeps the plan out of review. - Check what delegation guidance remains in the release: the discussion found only "keep doing useful work while they run", with no "end the turn when all that is left is waiting". Expected: you can find the same gap in your profile text.
- Run your own control: add "do not submit while delegations are in flight" to the policy and replay the trace. Expected: submission can still happen, which is the evidence that a hard gate is required.
Root cause: four checks in execute, and a projection that hides one-shot work
Bottom line: the checks are caller agent present, plan mode active for the session, plan starts with #, and userQuestions available; subagents, delegation directories and inboxes are never read.
- Compare against the source review in the discussion:
executeruns those four checks in that order and rejects only when one fails. Expected: you find the same structure in your local version. - Be precise about "running": it is the state of the subagent's own turn, not proof that its report has not been delivered. A guard that conflates them will misfire.
- Check the
list_agentsprojection: subagents whosemodeis notcontinuablereturn empty. Expected: one-shot delegations can neither be waited on nor listed. - Look for a blocking wait: the tree does have job tools with
wait, but jobs and delegations are separate surfaces, so it cannot join a subagent. - Net result: no enforced check, no visible waiting primitive, no list covering every delegation. Three gaps that stack.
Fix 1: deny in tools/pre-execute with a DSH plugin
Bottom line: no core change and shippable today — before each call, reject when the caller is a given agent, the tool is exit_plan_mode, and that agent has a subagent turn running.
- Write a DSH plugin that registers a
tools/pre-executelistener. Expected: every tool call passes through it. - Check
exec.name === 'exit_plan_mode'first and let everything else through. Expected: normal tool calls are unaffected. - Use
exec.agentto read its subagent list and filter entries whose turn state is running. Expected: you get ids and names. - On a match, return deny with a reason such as "N delegations are still running; end this turn, wait for the settlement notification, then submit the complete plan". Expected: the model reads that at the moment of the call, not from memory of a prompt.
- Attach the release condition to subagent settlement events, never to a countdown. Expected: you do not trade premature submission for a permanent block.
- State the coverage in the reason: it applies only to continuable subagents that can be proven running, and one-shot delegations are outside the visible set. Expected: readers do not overestimate it.
- Replay the trace from the first section. Expected: the call at step 139 is rejected, the parent ends its turn, and submission happens after the reports land.
- Observe one false positive: a call made just as a subagent settles but before the state refreshes. Expected: at most one extra turn of waiting, not a permanent denial.
Fix 2: check inside the DeepSeek Harness execute path and reject
Bottom line: if you can change core, put the gate in the tool itself — read live descendants before entering review and fail outright when any are running, so the review UI never opens.
- After the existing four checks in
execute, add a step that reads the current agent's subagent list. Expected: running delegations are visible. - Fail whenever a running delegation exists, and list the ids and names in the error. Expected: both user and model can see who is being waited on.
- Make sure this happens before asking the user, not after review with a rollback. Expected: the plan never reaches the review screen.
- Keep the
approvedoutput shape unchanged and only add a failure path. Expected: callers need no adaptation. - Update
plan:policyat the same time to require that a plan cover all delegated research and wait for in-flight work. Expected: the model does not have to learn by trial and error. - Add a regression case: start a background subagent and call
exit_plan_modeimmediately; assert failure with the subagent id in the message. - Add a one-shot case too. Expected: it is rejected as well, because the core can read all descendants rather than a filtered projection.
- Verify no plan text was written to the review record after the rejection. Expected: the user never sees v1.
Fix 3: disable the submit button in the DeepSeek Harness UI
Bottom line: this is the weakest fallback — it reduces misclicks but does not stop the model — so use it as an extra layer, not as a replacement.
- Subscribe the plan review screen to the session's subagent state. Expected: the button state follows the running count.
- Grey out approve or submit while anything is running, and show the number of in-flight delegations next to it. Expected: the user understands why it is disabled.
- Provide a read-only "view anyway" entry that previews the plan without approval. Expected: review is not completely blocked.
- State the limitation plainly: the model's
exit_plan_modecall ignores the button, and the plan may already exist. Expected: nobody treats this as a security boundary. - Replay the same trace. Expected: the UI shows two in-flight delegations, v1 cannot be approved, and the button unlocks after the reports land.
- Reload and retry. Expected: state rebuilds from session events rather than component memory, and the button stays disabled while anything runs.
Where the DeepSeek Harness gate belongs
Bottom line: the three intercept points differ in whether they can stop the model and in what they cannot see. Pick path one for immediate effect, path two for the correct fix, and treat path three as an addition.
| Path | Intercept point | Stops the model | Main limitation |
|---|---|---|---|
| pre-execute deny | Before the tool call | Yes | Cannot see one-shot work; can only reject, not wait |
| Check inside execute | Inside the tool | Yes, and most thoroughly | Core change, conflicts on upgrade |
| UI disable | Review screen | No | Manual fallback only |
What a badly written guard turns into
Bottom line: a gate that releases on a timer, or claims to see every delegation, converts a missed check into a different failure you will have to debug later.
- Do not rely on "list in-flight delegations in the review UI" alone; the trace already shows the model submitting anyway.
- The guard must release on a state change (subagent settlement), never on a timeout.
- "running" means the subagent turn is executing; it is not strictly the same as "the report has not arrived", so do not mix the two in user-facing text.
- One-shot delegations are a known blind spot; any guard claiming full coverage is inaccurate.
- The checks and code locations above come from community source review in the discussion; upstream has not fixed it, so treat this as temporary.
DeepSeek Harness FAQ
Bottom line: these four answers cover coverage, release conditions, and the difference between a hint and a gate.
Q: Does the guard block legitimate submissions forever if a subagent never finishes? A: No, but it will wait as long as the subagent does. That is why the release condition must be tied to settlement and why you need a way to cancel the subagent, not a timeout that silently lets the plan through.
Q: Can I combine the pre-execute gate with the UI disable? A: Yes, and it is sensible: the gate stops the model, the disabled button prevents a manual approval of a plan that should not be reviewed yet.
Q: Why not just increase the number of polls? A: Polling cannot create a precondition that does not exist. The model can poll indefinitely and still submit the moment the policy tells it to emit a tool call.
Q: Does this affect plan mode with no subagents at all? A: No. With no delegations, both the pre-execute gate and the execute check pass through, and the UI has nothing to disable.
FAQ
Because the DeepSeek Harness exit_plan_mode execute path checks only four things: the caller exists, plan mode is logically active for that session, the plan starts with a # heading, and the userQuestions channel is available. It never reads subagent state, so an unsettled delegation goes straight to review.
Yes — the DeepSeek Harness pre-execute hook is a waterfall that runs before every tool call and receives the calling agent and tool name, so it can return deny with a reason. It can only reject, not make the model wait, so the release condition must be actual subagent settlement, never a timer.
Because the DeepSeek Harness list_agents projection returns an empty result for subagents whose mode is not continuable. One-shot subagents are traversed but never listed, so the guard can only prove that the visible ones are running; the message must state that blind spot.
No — the DeepSeek Harness report's trace shows the model submitting after list_agents returned running, so a hint does not stop a stale plan from reaching review. Handing the decision to the user only downgrades a system problem to manual cleanup.
The DeepSeek Harness execute path can read all descendants, including the one-shot delegations the list_agents projection cannot see, so coverage is better. The price is a core change that must be re-aligned on every upgrade, which suits teams that can maintain a branch.
Related Terms
- exit_plan_mode
- The tool that submits a plan for review while in plan mode. It takes a single plan argument and returns an approved boolean. The current implementation checks only the caller, the mode state, the heading format and the question channel.— https://github.com/deepseek-ai/deepseek-harness/discussions/9876
- tools/pre-execute
- A waterfall hook that runs before each tool call. It receives the tool name, the parsed arguments and the calling agent, and may return allow, deny, cancel or ask. It fits admission checks based on caller state.— https://github.com/deepseek-ai/deepseek-harness/discussions/9876
- continuable subagent
- A subagent mode that stays interactive and therefore appears in the list_agents projection. One-shot subagents are absent from that projection, so any guard built on list_agents cannot see them.— https://github.com/deepseek-ai/deepseek-harness/discussions/9876
Sources
- exit_plan_mode presents a plan while background subagents it depends on are still running· deepseek-ai/deepseek-harness Discussions
- DeepSeek Harness official repository· deepseek-ai