dsh headless one-shot tasks: stdout, stderr and exit codes
dsh headless is a one-shot mode: start a fresh agent, submit the task, wait until it is fully settled, flush the session, then print the final text to stdout and reasoning to stderr. No port is opened and no browser client is mounted, which makes it a natural fit for CI and scripts. The contract is the important part: stdout is the answer, stderr is the process, and the exit code is the verdict.
DeepSeek Harness: Where it sits next to web and sdk
headless is not “web without a UI”; it is a separate one-shot execution path. Compare:(source)
| Mode | Lifetime | Listening port | Typical use |
|---|---|---|---|
dsh web | Long-running | Yes, 3080 by default | Human interaction |
dsh --profile headless | One task, then exit | No | CI, scripts, batch jobs |
dsh --profile sdk | Controlled by client | No, stdio | Embedding in code |
dsh --profile sdk-minimal | Controlled by client | No, stdio | Minimal SDK runtime |
headless mounts no browser connection, HTTP server, Web runtime, or browser client. Port conflicts cannot break it, and it does not leave a service to stop later.
DeepSeek Harness: What happens during one run
The order is fixed: create agent → submit task → wait for settle → flush → derive the result from persisted events. In detail:(source)
- Create a fresh agent — the core registry creates a persistent agent. Expected: the task gets its own session, not the previous conversation.
- Submit the task — the positional argument is the task text. Expected: the agent starts a normal turn.
- Wait until fully settled — not just “the model stopped streaming”, but the agent lifecycle reaches idle. Expected: tool calls and subagents finish.
- Flush the session — persistence buffers are written. Expected: a later read sees a complete event range.
- Derive the result — take the last non-empty assistant text and the last
turn/endreason. Expected: stdout gets the answer, the exit code reflects the reason.
This also explains apparent hangs: the model may be done but the agent is still waiting for a tool or subagent to settle. headless does not emit a half-finished result as final.
DeepSeek Harness: The stdout, stderr, and exit-code contract
stdout is the answer, stderr is the process, and the exit code is the verdict; do not mix them. The contract:(source)
| Channel | Content | Recommended use |
|---|---|---|
| stdout | Last non-empty assistant text | Redirect to a file or pipe |
| stderr | Reasoning under dsh: reasoning: | Keep as a log, not an answer |
| exit 0 | turn/end reason is completed | Treat as success |
| exit 1 | Other terminal reasons or failure | Treat as failure and keep stderr |
Example:
dsh --profile headless "run the tests and summarize failures" \
> /tmp/dsh-answer.txt \
2> /tmp/dsh-reasoning.log
- Read the exit code first —
echo $?. Expected: continue only on 0; inspect stderr on 1. - Read stdout second — take only the final text. Expected: no reasoning heading, tool logs, or progress bars.
- Read stderr last — reasoning and errors live here. Expected: distinguish “the model thought for a while” from “a tool failed”.
Do not merge streams with 2>&1 and then parse; once merged, stdout is no longer a clean answer channel.
DeepSeek Harness: Signals, exit, and resource release
On SIGINT or SIGTERM, headless disposes the mounted root before exiting rather than being killed abruptly. This matters for graceful cancellation:(source)
- Send SIGTERM — let the process release resources and finish in-flight tool calls. Expected: it exits and the session remains flushed up to the last checkpoint.
- Do not jump straight to SIGKILL — a hard kill can leave an unflushed session tail. Expected: give SIGTERM a window first.
- Treat signal exits as failure or incomplete — they are usually not 0. Expected: scripts do not mistake cancellation for success.
headless mounts no HTTP server or browser client, so there is no port to release; what it releases is session persistence and tool subprocesses.
DeepSeek Harness: Three CI patterns
headless fits “one input, one output, one exit code”; different jobs only change the wrapper. Three patterns:(source)
- Direct command —
dsh --profile headless "$PROMPT". Expected: simple jobs with a shell exit-code check. - Wrapper script — pin the workspace and
DSH_HOME, write stdout as an artifact. Expected: jobs do not share profiles or sessions. - Scheduled trigger — cron or a CI scheduler starts a new session each time. Expected: context does not grow across unrelated runs.
If a task must continue a previous conversation, headless is the wrong entry point; it creates a fresh agent each time. Use the SDK or an explicit session-reuse mode.
DeepSeek Harness: Common traps
Most headless failures are contract misreads, not a model that refuses to work. Four traps:(source)
- Parsing stderr as the answer — reasoning deltas live there too. Expected: read only stdout.
- Ignoring the exit code — text can exist while the turn is cancelled or failed. Expected: non-zero means failure.
- Empty task text — no positional argument is a usage error. Expected: validate the variable before invoking.
- Sharing one home across jobs — profiles, plugins, and sessions leak between runs. Expected: set
DSH_HOMEexplicitly in CI.
Debug in this order: exit code, the end of stderr, then stdout. Reversing the order lets a plausible-looking answer hide a failed run.
DeepSeek Harness FAQ
Can headless open the Web UI port?
No. It mounts no HTTP server and opens no listening port; use dsh web when you need an interface.
Does one headless run leave session files?
Yes. It creates a persistent agent and flushes the session, so $DSH_HOME/sessions contains a record; apply your normal retention policy.
Can I use headless as a long-running headless service? Not really. It is one-shot and exits when the task finishes; a long-running service should use web or an SDK client.
The short version: headless is valuable because the contract is crisp—stdout is the answer, stderr is the process, exit code is the verdict.
FAQ
DeepSeek Harness: dsh headless creates a fresh persistent agent, submits one task, waits until it is fully settled, flushes the session, and exits. It mounts no browser connection, HTTP server, or Web runtime and opens no listening port. dsh web is the long-running Web UI.
DeepSeek Harness: stdout carries only the final assistant text. Non-empty provider reasoning deltas go to stderr under the dsh: reasoning: heading. A successful response with no reasoning leaves stderr empty.
DeepSeek Harness: the exit code reflects the final turn/end reason, not whether text exists. Only completed returns 0; cancellation, errors, and other terminal reasons return 1 even when partial output was printed. Check the code and stdout together.
DeepSeek Harness: it is a usage error. The task text is a positional argument for headless; without it the invocation is invalid. It does not enter an interactive prompt or read a default file.
DeepSeek Harness: yes. It opens no port, prints only the final answer, and expresses completion through an exit code. Each run creates a persistent agent session, so isolate workspaces and DSH_HOME across jobs.
Related Terms
- headless
- headless is the one-shot DeepSeek Harness profile started with dsh --profile headless <task>. It runs a fresh persistent agent, waits for it to settle, flushes the session, and exits.— DeepSeek Harness reference - dsh CLI behavior
- turn/end
- turn/end is the persistent session event that closes a turn with a terminal reason. headless uses the last turn/end reason to choose its process exit code.— DeepSeek Harness reference - dsh CLI behavior
- flush
- flush writes session logs and persistence buffers to disk. headless flushes the session before exiting so the final text comes from a persisted event range.— DeepSeek Harness reference - dsh CLI behavior
- reasoning delta
- A reasoning delta is a streaming chunk of model thinking, separate from the final visible text. headless writes these chunks to stderr so stdout stays a clean answer channel.— DeepSeek Harness reference - dsh CLI behavior
Sources
- DeepSeek Harness reference - dsh CLI behavior· deepseek-ai
- DeepSeek Harness reference - Subprocesses· deepseek-ai