PerryLink
dsh-background-agents
Interactive long-session background agents for DeepSeek Harness: start a durable continuable child agent, watch its progress in the Web UI sidebar, message it any time, and interrupt it - all through the official subagent seam.
- Stars
- 1
- Language
- JavaScript
- Created
- Aug 14, 2026
- Updated
- Aug 14, 2026
Introduction
dsh-background-agents
Interactive, long-session background agents for DeepSeek Harness. Start a durable child agent that keeps working while you keep talking 鈥?watch its progress, steer it with messages, and stop it, all without leaving your session.
English 路 涓枃 路 Espa帽ol 路 Portugu锚s 路 啶灌た啶ㄠ啶︵
DSH's built-in background jobs are fire-and-forget tool executions: you can read output and kill them, but you cannot talk to them. dsh-background-agents upgrades that to full background agent sessions on the official subagent seam 鈥?a continuable child conversation you can message, steer, and interrupt at any time, while an injected progress line after each of its turns keeps you (and the model) in the loop.
What you get
background_agent鈥?start a durable, continuable child agent from any session. It runs in its own context, returns a stable agent id immediately, and keeps its conversation open forever. Optional per-child scoping:tool_filter(removes tools from the child's view 鈥?never grants new ones),persona(a dedicated system-prompt persona), andmax_depth(a delegation-depth cap);childProvider/childModelconfig route its model requests.bg_message鈥?send it more work, corrections, or wake a settled agent. Delivered through the official FIFO inbox; the agent's answer is its next turn.bg_list鈥?status of your agents: label, mode, activity (running/idle/ready/settled/archived), message count, last activity time. Recovers persisted children after a restart.recursive: truelists the whole descendant tree withparentId/depth.bg_result鈥?fetch a child's latest assistant output text plus its activity, beyond the settled-notice summary.bg_stop鈥?request interruption of the current turn. Fire-and-return: official teardown finishes the job; the agent stays resumable.- autoReport 鈥?after every child turn, one throttled progress line is injected into your session (model-visible, plugin-sourced). Its final outcome arrives via the official settled notice.
reportDelivery: wakeupmakes each line start a parent turn when the parent is idle. - Idle archive 鈥?agents quiet past
idleTimeoutMinutesare archived with a notice and a stop request;bg_messagewakes them back up. backgroundAgentsprojection 鈥?a session-projection unit that folds the parent log into dashboard rows (agent id, label, activity, last message summary, created time). Everything reconstructs from the durable log 鈥?no separate database.- Web UI panel 鈥?a "Background agents" entry in the Web GUI sidebar with live status, one-click jump into the child session, a stop button, and a message button that queues a new turn through the official
subagent.promptRPC.
Quick start
# from the harness checkout or wherever the dsh CLI lives (web or headless)
dsh plugin --profile <name> add "github:PerryLink/dsh-background-agents#v0.3.0"
The bundle patch carries the plugin row, so dsh plugin add composes it into your profile's layer stack (dsh.profile.bundles). Prefer the git source with a pinned ref: the repo commits its build output (lib/), so git installs need no build step and no allowBuilds entry. The package is also published to npm — plain pnpm add dsh-background-agents works (CI publishes every tag push).
The row that lands in your profile (override config per profile in cordis.patch.yml):
- insert:
- id: background-agents
name: dsh-background-agents
config:
provider: spawn # the ctx.subagents provider for continuable children
The plugin needs the subagent spine already mounted (any profile built on @deepseek-ai/dsh-base has it: dsh-subagent, dsh-subagent-spawn-in-process, dsh-session-projection).
Then, in any session, just ask the model 鈥?or call the tools directly:
background_agent "watch the repo for test failures and keep me posted" (label: test-watch)
bg_list
bg_message <agentId> "also check the snapshot tests now"
bg_stop <agentId>
Configuration
Every tunable is a validated Config field 鈥?change it in cordis.yml, never in code.
| Field | Default | Meaning |
|---|---|---|
provider | (required) | ctx.subagents provider name for continuable starts (spawn) |
autoReport | true | inject one progress line into the parent after each child turn |
reportDelivery | quiet | quiet appends the line to the parent's next model request; wakeup starts a parent turn when idle (queues when busy) |
reportThrottleMs | 15000 | minimum gap between two progress injections for one child |
reportSummaryMaxChars | 300 | hard cap on the injected progress-line text (ellipsized) |
resultMaxChars | 4000 | hard cap on the bg_result text (ellipsized, flagged truncated) |
maxBackgroundAgents | 4 | hard cap on non-archived background agents per parent session; the budget is shared by every continuable direct child of the session (including ones the built-in subagent tool started) |
idleTimeoutMinutes | 120 | idle window after which a quiet child is archived and notified (>= 1) |
idleSweepIntervalMs | 60000 | archive sweep period |
maxLabelChars | 120 | display-label cap (ellipsized) |
childProvider | (inherit) | provider route for child model requests |
childModel | (inherit) | model id for child model requests |
maxChildDepth | (none) | config ceiling for a start's max_depth argument |
allowedChildTools | (none) | allowlist for tool_filter names; empty/absent = no limit |
How it works 鈥?and why it survives restarts
Everything rides the official subagent seam: startContinuable, followup, interrupt, listChildren 鈥?the plugin performs no lifecycle routing of its own, never touches another session's Agent, and never kills a process tree (stop = request interruption, teardown belongs to the continuation manager).
The plugin writes every fact through one structured channel and one model-visible channel:
background-agents/factstructured fact events (v0.3.0+) 鈥?the registered / message / stop / progress / archived facts, appended to the parent log as log-only records with the envelope'signorable: truemarker; readers that do not know the type skip the records instead of refusing the log, so older harness builds and older plugin versions still open parents written by this one;tool/resultreplay metadata 鈥?the same facts in logs written before v0.3.0 (folded only while a row has no structured provenance);- injected
user/messagenotices (model-visible), source{ kind: 'plugin', plugin: 'dsh-background-agents' }鈥?the throttled progress lines and archive notices (canonical `[background-agent ] 鈥 prefix); - the official
subagent-settlednotice 鈥?the child's durable "settled" fact.
The backgroundAgents projection unit folds the structured channel and keeps the legacy folds for pre-v0.3.0 logs (a row switches to structured provenance on its first fact, so a dual-channel log never double-counts). The dashboard value and bg_list facts reconstruct on every reopen without parsing human-readable notice text. When the catalog itself is unavailable (projections or session store missing), bg_list returns an explicit unrecoverable marker 鈥?it never fabricates an empty list.
Not this plugin
| Project | What it does | The boundary |
|---|---|---|
| titanwings/dsh-automation | Scheduled coding tasks in fresh agent sessions | It owns when tasks run (scheduling). This plugin owns interactive steering of one long-lived conversation 鈥?no scheduler seam, no cron. |
| vlln/dsh-task-status | Status bar for background jobs (progress + output tail) | It displays tool-level jobs. This plugin creates and steers agent sessions; its dashboard is one panel of it, not the product. |
| YYTbit/dsh-plugin-agent-dashboard | Multi-agent dashboard skill | Display-oriented. This plugin's rows are actionable: jump into the child session, send messages, stop 鈥?through the official control plane. |
How this relates to the built-in subagent tools
The harness core ships its own subagent tools (subagent, send_message, interrupt_agent, and the child-side report tool). This plugin's bg_* tools are their session-scoped companions; both can be mounted together:
| Built-in tool | This plugin | Difference |
|---|---|---|
subagent (backgroundMode: 'continuable') | background_agent | Same startContinuable seam; this plugin adds per-child tool_filter/persona/max_depth validation and the per-session cap |
send_message | bg_message | Same delivery semantics; bg_message addresses this conversation's background agents and maintains the projection facts |
interrupt_agent | bg_stop | Same interrupt semantics; bg_stop also records a structured stop fact |
child-side report tool | autoReport | The built-in is called by the child model itself; this plugin injects throttled progress after every child turn automatically |
What the core tools lack: bg_list, bg_result, idle archiving, and the per-parent folded panel projection.
Not in scope: scheduled triggering (the schedule seam exists), cross-machine/remote agents, and any change to the official subagent activation contract.
Development
pnpm install # tooling only; harness packages resolve against a sibling checkout
pnpm run typecheck # strict TS, node + client programs
pnpm test # 69 unit + end-to-end tests (real subagent seam, scripted LLM, jsdom panel)
pnpm run build # lib/index.js (node half) + lib/client.js (web client bundle)
pnpm run gen-aliases # re-map harness package paths after the checkout moves
A keyless end-to-end demo drives a real parent session and a background child through a deterministic scripted LLM (no API key; dev/ is gitignored 鈥?adapt the paths to your checkout):
$env:DSH_HOME = 'D:/deepseek-harness/Project/Plugins/dsh-background-agents/dev/dsh-home'
pnpm dsh --profile headless --patch dev/cordis.yml "銆愮埗浼氳瘽銆戦┍鍔ㄥ悗鍙?agent 婕旂ず"
The test suite covers the full path 鈥?start, list, message, stop 鈥?against the real SubagentRuntime with the in-process spawn provider and a scripted adapter, plus throttle/cap/archive policy, projection folding, and crash recovery through session-persistence-jsonl.
License
Apache License 2.0 鈥?see LICENSE. Third-party notices: THIRD_PARTY_NOTICES.md.