HEO-Club
DSH-DAG
No description
- Stars
- 2
- Language
- TypeScript
- Created
- Aug 14, 2026
- Updated
- Aug 16, 2026
Introduction
DSH Multi-Agent DAG Plugin
English · 中文(简体)
Give your DeepSeek Harness agents the power to run complex multi-agent workflows, declaratively: break one big task into a graph of smaller agent subtasks, run the independent ones in parallel, and combine the results — with deterministic, code-enforced orchestration.
What it is
The DSH main agent is a single long-running turn. Fanning out to many agents by hand — some subtasks depending on others, results flowing back and forth — is error-prone and doesn't scale. DSH already ships imperative fan-out (subagent, workflow), but nothing that declaratively describes a dependency graph and lets the machine run it.
This plugin fills that gap. The model (or any caller) submits a small JSON description of the workflow — nodes, dependencies, success criteria — and the plugin takes care of the rest:
- it validates the graph up front and rejects bad proposals before anything runs,
- it schedules ready nodes in parallel under concurrency limits,
- it retries bounded failures with exponential backoff,
- and it fuses the successful results into one final answer.
You describe what the workflow is. The plugin handles how it runs.
Capabilities at a glance
| Capability | What it does |
|---|---|
| Declarative input | A JSON task graph: nodes, dependsOn, inputSources, success criteria, optional per-node model |
| Deterministic validation | Duplicate ids, unknown dependencies, cycles, missing input sources… invalid proposals are rejected before any run starts, with structured, model-correctable errors |
| Parallel scheduling | Independent nodes run concurrently under global + per-model limits; dependent nodes are released only when every parent succeeds |
| Node state machine | PENDING → READY → RUNNING → SUCCEEDED / FAILED / CANCELLED, failures cascade as BLOCKED, full audit trail |
| Retry & recovery | Only retryable errors retry, with bounded exponential backoff; per-node timeouts; clean cancellation propagation |
| Result aggregation | A single result passes through; many results are fused by one LLM call (or deterministically deduped and concatenated) |
| Observability | dag/* lifecycle events on the host, optionally recorded into the calling agent's session |
How it works
DSH main agent
│ the model calls the dag_run tool (or a plugin calls ctx.dag.start())
▼
dsh-dag the Cordis plugin — tool, service, config, events
│
▼
dag-core the framework-free engine — model, validation, analysis,
scheduler, state machine, retry, fusion
│
▼
Parallel agent execution — concurrent child agents via DSH subagents
Every run follows one deterministic pipeline:
- Compile & validate — the proposal is checked; an invalid graph returns fixable errors and no run is created.
- Execute by wave — ready nodes run as concurrent child agents; each settlement releases (or blocks) its dependents.
- Retry & fuse — bounded retries with validation feedback; successful results are fused into one final answer.
Quick start
1. Install the plugin into a DSH profile
dsh plugin --profile <name> add dsh-dag
Installing applies the plugin's bundle patch, which inserts the dsh-dag row into the host composition and provides the dag service on the host plane. Verify it landed:
dsh --profile <name> --dump-config | grep dsh-dag
2. Expose the tool to your agents
The dag_run tool is exposed per agent by composing a preset copy with an isolated dag realm (mirroring how the built-in delegation group isolates workflowEngine):
- id: dag-delegation
name: cordis:group
group: true
isolate:
dag: true
config:
- id: dsh-dag-tool
name: 'dsh-dag'
3. Use it
Ask your agent for a multi-step task, or call the tool directly with a workflow like the one below.
Using the dag_run tool
Submit a TaskGraphProposal-shaped JSON:
{
"schemaVersion": "1.0",
"planId": "research_plan", // ^[a-z][a-z0-9_-]{0,63}$
"objective": "Research X and write a report",
"nodes": [
{
"nodeId": "search",
"title": "Search the literature",
"prompt": "Find and summarize the top sources on X.",
"capabilityRequirements": ["web"],
"outputRequirements": ["A bullet list of sources"],
"successCriteria": ["at least 3 sources"],
"executorKind": "runtime",
"toolLabels": ["web_search"],
"dependsOn": [],
"inputSources": []
},
{
"nodeId": "draft",
"title": "Draft the report",
"prompt": "Write the report from the search results.",
"dependsOn": ["search"],
"inputSources": [{ "sourceNodeId": "search", "purpose": "use search results" }],
"capabilityRequirements": ["general"],
"outputRequirements": ["Markdown report"],
"successCriteria": ["covers all sources"],
"executorKind": "runtime"
}
]
}
Invalid proposals never create a run — you get back structured, correctable errors instead.
Result envelope
{
"runId": "dag_a1b2c3d4",
"status": "completed", // completed | partial | failed | cancelled
"value": "…fused final answer…",
"nodeCount": 2,
"agentsStarted": 3,
"failures": [{ "nodeId": "draft", "status": "failed", "error": "…" }]
}
Anything other than completed surfaces as a tool error — never as a silent success.
Programmatic entry
const run = ctx.dag.start({
proposal, // TaskGraphProposal
parent: exec.agent,
options: { idempotencyKey?, nodeTimeoutMs?, maxTotalNodes?, fusion?, globalLimit?, maxRetries? },
})
const outcome = await run.result
await run.dispose()
Configuration
| Key | Default | Meaning |
|---|---|---|
toolName | dag_run | The model-facing tool name |
subagentProvider | spawn | ctx.subagents provider used for every node (and fusion) call |
globalLimit | 4 | Global node concurrency (0 = unlimited) |
perModelLimits | {} | Per-model node concurrency (0 = unlimited) |
retryPolicy.maxRetries | 2 | Per-node retry ceiling |
retryPolicy.baseDelaySeconds | 1 | Exponential backoff base |
retryPolicy.maxDelaySeconds | 30 | Backoff cap |
nodeTimeoutSeconds | 300 | Per-node timeout (0 disables) |
maxTotalNodes | 32 | Hard node-count cap per run |
maxResultChars | 100000 | Cap on dependency outputs injected into downstream prompts |
fusion | auto | auto: single result passes through, many fuse via LLM; llm: always fuse; none: deterministic concatenation |
emitSessionEvents | true | Record dag/* events into the calling agent's session |
Observability
Observe-only dag/* events are emitted on the host and (optionally) appended to the calling agent's session:
dag/run-start · dag/node-start · dag/node-end · dag/retry · dag/run-end
Payloads carry scalar facts only — never live handles.
Guarantees and limitations
What you can rely on
- Determinism is code-enforced: graph legality, state transitions, concurrency, retry caps and dependency propagation are all enforced by the engine — the model decides, the code enforces.
- Safe cancellation:
run.cancel()/exec.signalaborts in-flight children; every child run is disposed.
Current limitations (V0.1)
- Foreground-only runs — no background start/poll, no journaling/resume.
- No budget/cost ledger; concurrency defaults conservatively to bound token spend.
- The plugin never decides single- vs multi-agent — the model calls
dag_runonly when it judges the task genuinely needs a DAG. - Delegated children inherit DSH sub-agent policy (approval
never, sandbox scope inherited); nodes that need approval-gated tools fail deterministically.
Uninstall
dsh plugin --profile <name> remove dsh-dag
Then remove the composed preset copy (e.g. the dag-delegation group above) from your agent preset and restart the session. Active runs are cancelled automatically on unload.
For developers
Quick orientation — the full engineering specification and migration plan lives in
docs/DSH Multi-Agent DAG Plugin — Engineering Specification & Migration Plan.md.
packages/
├── dag-core/ dsh-dag-core — framework-free orchestration engine (zero runtime deps)
└── dsh-dag/ dsh-dag — the Cordis plugin adapter
npm install --ignore-scripts # workspace install (typescript + vitest)
npm run build # tsc → packages/*/lib
npm run typecheck
npm test # 110 deterministic tests — no network, no real LLM
dag-core is a faithful TypeScript port of a proven Python orchestration middle layer, kept 1:1 for parity (component mapping in the spec above). The plugin adapter is deliberately thin: everything orchestration-related lives in dag-core, everything DSH-related lives in dsh-dag.
License
MIT