DeepSeek Harness Connector¶
Status: public-safe v0 connector for using DeepSeek Harness (dsh) as a
bounded agent execution host behind LoopX.
DeepSeek Harness is an open-source agent harness by DeepSeek AI. LoopX does not replace dsh's model loop, tools, sandbox, or session log. Instead, the connector lets LoopX govern one dsh-backed work segment at a time through the existing LoopX Turn protocol:
LoopX quota should-run
-> loopx turn run-once
-> loopx.dsh_goal_mode adapter (python -m loopx.dsh_goal_mode)
-> DeepSeek Harness Python SDK / dsh runtime
-> typed loopx_turn_result_v0
-> independent validator
-> LoopX writeback + quota spend
What This Connector Adds¶
- A thin adapter, now a first-class goal-mode subpackage at
loopx/dsh_goal_mode/(run withpython -m loopx.dsh_goal_mode; the historicalscripts/dsh_turn_host_adapter.pylauncher still works), that translatesloopx_turn_host_request_v0into one bounded dsh session prompt and parses the model's final JSON result back intoloopx_turn_result_v0. - A
deepseek-harnessagent type in LoopX onboarding so users can request the exact host instead of the genericother-agent. - Optional dependency
loopx[deepseek-harness]for the validateddeepseek-harness-sdk==0.1.5rc1Python client.
Install¶
Install LoopX's optional DeepSeek Harness extra:
The pin tracks the newest published dsh release channel rather than an
unreleased tag: 0.1.5rc1 for the PyPI SDK/runtime wheels and 0.1.5-rc.1 for
the npm @deepseek-ai/dsh latest tag. Upstream also publishes newer
next/alpha tags that are not the released channel. This release keeps the
Python client surface of the previously pinned 0.1.2a3 and moves the bundled
dsh runtime; LoopX selects the SDK's default sdk profile unless the operator
supplies an explicit cordis composition.
The DeepSeek Harness SDK spawns the bundled dsh-jsonrpc-agent runtime. It
uses the explicit adapter configuration plus normal provider environment
variables:
The adapter resolves its SDK home in this order: explicit --dsh-home, then
DSH_HOME, then <workspace>/.local/.dsh-sessions. It passes that path as the
SDK's dsh_home field; the SDK does not implicitly select ~/.dsh.
Prepare a dsh cordis.yml when the default bundled composition is not
appropriate. See the
DeepSeek Harness Python SDK reference
for runtime selection and configuration.
Onboard¶
loopx doctor --agent-type deepseek-harness
loopx agent-onboard \
--agent-type deepseek-harness \
--project . \
--goal-id <goal-id> \
--agent-id deepseek-worker \
--available-capability shell
deepseek-harness maps to the generic CLI agent loop and uses
--runtime-profile generic_cli in quota/heartbeat commands.
Run One Governed Turn¶
loopx turn run-once \
--goal-id <goal-id> \
--agent-id deepseek-worker \
--host generic-cli \
--execution-mode isolated-headless \
--project "$PWD" \
--host-adapter-command-json '["python3", "-m", "loopx.dsh_goal_mode", "--dsh-home", "/path/to/dsh-home", "--cordis", "/path/to/cordis.yml", "--model", "deepseek-v4-flash"]' \
--validation-command-json '["python3", "/path/to/verify-postcondition.py"]' \
--execute
The adapter uses <workspace>/.local/.dsh-sessions/ as its workspace-local
SDK home by default. Override it with --dsh-home <path>; the historical
--session-root spelling remains an adapter-command compatibility alias.
Session persistence itself is owned by the selected dsh composition and is not
implied by the home-directory name.
The headless adapter derives its local session id from a versioned digest of
[goal_id, agent_id, todo_id], preserving component positions so delimiters
inside an id cannot alias another lineage. The same lineage produces the same
id on retries; when all three components are absent, the existing Turn-key
fallback is retained. These opaque ids stay out of public LoopX state.
Upgrading from the former hyphen-joined naming scheme selects a new session id for a populated lineage. The adapter does not fall back to the ambiguous old name, rename sessions, or delete existing session files. Any persistence or resume behavior for the newly selected id remains owned by the dsh composition.
Run One Governed Turn In Process (--host dsh)¶
The built-in host runs the same adapter inside the CLI process:
loopx turn run-once \
--goal-id <goal-id> \
--agent-id deepseek-worker \
--host dsh \
--execution-mode isolated-headless \
--project "$PWD" \
--dsh-home /path/to/dsh-home \
--dsh-cordis /path/to/cordis.yml \
--dsh-model deepseek-v4-flash \
--validation-command-json '["python3", "/path/to/verify-postcondition.py"]' \
--execute
Unlike the subprocess mode, provider failures reach the Turn journal as typed
loopx_turn_host_failure_v0 kinds (including the SDK's exception-free
RunResult.finish_reason == "error" terminal report), so bounded same-Turn
retry stays available. This mode does not promise cross-turn dsh session
continuity or an outer wake/timer. See the adapter README for the home and
classification precedence, plus the hermetic verification smoke
(examples/loopx-turn-dsh-builtin-host-e2e-smoke.py).
Host Selection And Managed Executor Readback¶
The Turn host is selected, never inferred from an incidental environment. An
explicit --host (or --host-adapter-command-json) or LOOPX_TURN_HOST always
wins. With neither configured, the shipped default is resolved from the
operator's own credential facts: dsh is the default when DEEPSEEK_API_KEY is
configured, because it is the managed execution unit the steward drives and that
credential authenticates it, and codex-cli is the default when no credential is
configured, because an unauthenticated managed host would refuse to run.
What runs on that host is a separate resolution. The managed execution profile
defaults to deepseek-official / deepseek-v4-flash / high, overridden by
LOOPX_TURN_PROVIDER / LOOPX_TURN_MODEL / LOOPX_TURN_REASONING_EFFORT and, at
lower precedence, by the legacy DSH_PROVIDER / DSH_MODEL; an explicit
--dsh-provider / --dsh-model / --dsh-reasoning-effort wins over both. The
steward channel resolves the same profile when it selects the managed host, so
the channel and the bounded Turns it drives cannot land on two different managed
models.
The credential those two surfaces authenticate with is a machine setting rather
than a launch-file variable: loopx machine-config credential set, the
Dashboard's machine capability settings, or the service environment, resolving
in that order. The key is write-only and never enters the machine-configuration
document, so an operator changes it from a product surface instead of editing a
launch file and restarting the service. See
Operator Model Credential.
Both loopx turn plan and loopx turn run-once report a managed_executor
block, so a caller reads the planned executor instead of inferring it from a
host id:
{
"schema_version": "managed_executor_binding_v0",
"executor": "dsh",
"executor_kind": "managed",
"credential_env": "DEEPSEEK_API_KEY",
"endpoint_env": "DEEPSEEK_BASE_URL",
"operator_credential_bound": true,
"execution_profile": "deepseek-v4-flash@high",
"output_token_budget": {
"schema_version": "dsh_output_token_budget_v0",
"scope": "per_model_request",
"max_tokens": 16384,
"valid": true,
"source": "product_default",
"final_response_reserve_supported": false,
"hard_tool_budget_supported": false
},
"available": true,
"unavailable_reason": null
}
executor_kind names where the Turn's model work is billed and bounded:
managed for a host bound to an operator credential, individual for a host
that runs on one person's own CLI login, and generic for a caller-supplied
adapter command. operator_credential_bound is the narrower claim: it is true
only when the operator credential or an explicit injected runner hook is
configured. available is false only when LoopX can prove the planned host
cannot launch here, and null for executors this projection does not probe
rather than an unproven claim. Only the credential variable name is reported;
the value is never read back. execution_profile is the resolved profile as one
line, deepseek-v4-flash@high in the shipped shape, with the provider prepended
only when it is not the shipped one -- it is one line because every plan carries
it, and the agent-facing output budget is a contract.
output_token_budget is deliberately explicit about scope. With the validated
DeepSeek Harness 0.1.5rc1 runtime, maxTokens caps each conversation-model
request, not the sum of a tool-using Turn, and reasoning tokens are part of the
reported output-token count. LoopX uses a product default of 16384 for both managed Turns and Chat
segments, replacing the 256000 default observed on this SDK route. Explicit
positive --dsh-max-tokens values retain their meaning; the cap reduces the
maximum output allowance and is not a promise that a request will fit.
The SDK exposes neither a hard tool-call budget nor a final-response reserve,
so both are reported as unsupported instead of inferred. A max-tokens stop is
output_budget_exhausted, never ordinary success or an automatic same-request
retry; no_final and partial are distinguished in the public-safe failure
reason while the raw session remains local.
output_token_budget 会明确说明预算作用域。经验证的 DeepSeek Harness
0.1.5rc1 中,maxTokens 限制每次模型请求,而不是整个含工具调用的 Turn;推理
token 也计入输出 token。LoopX 使用 16384 的有界产品默认值,不静默继承适配器
更大的精确路由默认值。SDK 没有提供硬工具调用预算或最终答复预留,因此控制面会
明确标记为不支持。托管 Turn 与 Chat segment 使用同一默认上限,替换该 SDK
路由原有的 256000;显式正整数上限仍有效,有界默认值不保证请求必定完成。
max-tokens 必须归类为 output_budget_exhausted,不能当作
普通成功或自动重试;公开安全的 reason 区分 no_final 与 partial,原始 Session
仍只保留在本地。
runtime_probe distinguishes an import probe (scope: "probing_interpreter",
module: "deepseek_harness") from an injected runner (scope: "configured_runner",
module: null, no import attempted). Availability applies to the answering
interpreter or runner, not the whole machine, and does not prove provider
authentication. The Chat refusal directs the operator to run loopx doctor
in the service environment, check python.executable, and install the SDK in
that same environment before restarting. Interpreter paths stay in local doctor
output, outside the Turn payload.
run-once --execute fails closed on that verdict: status unavailable, no host
invocation, no journal write, and no quota spend, with
dsh_runtime_unavailable, operator_credential_unconfigured, or
invalid_reasoning_effort / invalid_output_token_limit naming the missing
fact. plan reports the same verdict without refusing.
Boundaries¶
- LoopX keeps the durable goal, todo, claim, gate, quota, evidence, and scheduler authority.
- dsh owns model calls, tools, sandboxing, and the raw session log.
- The adapter must not publish raw transcripts, dsh JSONL sessions, credentials, local absolute paths, or unbounded tool output into LoopX state.
DeepSeekHarness.run()returns the candidate result; it is not proof of completion. An independent validator is required before LoopX writeback.- The dsh Python SDK is an optional dependency. Core LoopX remains runtime dependency-free.
- The adapter derives an owner-local session id, but LoopX does not project or validate a DSH Host Session Binding. This surface therefore does not claim a managed supervisor or cross-process resume guarantee.
Hermetic Validation¶
The repository includes four validation paths. The first three do not require the DeepSeek Harness SDK or a real dsh runtime:
python3 examples/dsh-turn-host-adapter-smoke.py
python3 examples/loopx-turn-dsh-e2e-smoke.py
python3 examples/loopx-turn-dsh-builtin-host-e2e-smoke.py
The first guards adapter translation and result shaping. The second drives the
full loopx turn run-once -> adapter -> fake dsh -> validator -> writeback ->
quota spend -> idempotent replay chain. The third proves the built-in host's
success path plus three bounded provider-capacity attempts, retry-budget
exhaustion without a fourth Host invocation, zero failure spend/writeback, and
provider-prose non-persistence.
The fourth uses the real deepseek-harness-sdk and the bundled dsh JSON-RPC
runtime. It still avoids a real model call by serving a local mock OpenAI-compatible
SSE endpoint, so it is hermetic and does not require DEEPSEEK_API_KEY:
python3 examples/loopx-turn-dsh-real-e2e-smoke.py --host generic-cli
python3 examples/loopx-turn-dsh-real-e2e-smoke.py --host dsh
The real-dsh smoke clears ambient DSH home variables and proves that both host paths can supply an explicit SDK home, start the actual dsh runtime, run one bounded turn through the real JSON-RPC agent loop, parse a typed JSON final message, and complete LoopX validation/writeback/quota spend.