DeepSeek Harness setup
DeepSeek Harness (dsh) is DeepSeek's open-source Agent Harness and is currently in Developer Preview. Its plugin architecture composes the Agent, models, tools, and Web UI, and it supports custom OpenAI-compatible gateways as model providers.
Install Node.js first, then create a Lumoswitch API configuration with OpenAI-compatible output. The commands below launch the local Web UI through the official npx @deepseek-ai/dsh web entry point.
Choose a setup method
| Method | Best for | Local writes | Still active after exit? |
|---|---|---|---|
| One-time launch | Testing a URL, Key, and model catalog | Temporary Lumoswitch overlay; normal Harness runtime data remains | No |
| Persistent use | Starting the local Web UI from one path | Native DSH home + optional launcher | Yes |
Neither method replaces $DSH_HOME/settings.yaml, the default profile patch, or an existing provider. Persistent setup stores the Access Key in the dedicated DSH home's owner-only .env file.
Prepare the connection values
| Placeholder | Value |
|---|---|
{{api_base_url}} | OpenAI-compatible API configuration URL, including /v1 |
{{access_key}} | The Lumoswitch Access Key selected for this configuration |
{{model}} | The client-facing model selected as the initial default |
{{deepseek_harness_models_yaml}} | Harness YAML model list generated from every downstream model and its effective reasoning capability |
The console writes every downstream model into the overlay. It publishes reasoning levels only when the effective transport is known, explicitly disables reasoning controls for non-reasoning models, and does not guess for fixed or unknown capabilities.
One-time launch
Run this from the project directory that should be offered as the default workspace. The temporary directory contains only this launch's Lumoswitch overlay and is removed when the process exits:
(
set -e
LUMOSWITCH_DSH_TMP="$(mktemp -d "${TMPDIR:-/tmp}/lumoswitch-dsh.XXXXXX")"
LUMOSWITCH_DSH_PATCH="$LUMOSWITCH_DSH_TMP/lumoswitch.cordis.yml"
trap 'rm -f "$LUMOSWITCH_DSH_PATCH"; rmdir "$LUMOSWITCH_DSH_TMP" 2>/dev/null || true' EXIT
cat > "$LUMOSWITCH_DSH_PATCH" <<'LUMOSWITCH_DSH_PATCH_EOF'
- id: agent-default-model
config:
provider: lumoswitch
model: "{{model}}"
- id: llm-pi-ai
config:
providers:
lumoswitch:
displayName: Lumoswitch
apiKeyEnv: LUMOSWITCH_DEEPSEEK_API_KEY
api: openai-completions
baseURL: "{{api_base_url}}"
models:
{{deepseek_harness_models_yaml}}LUMOSWITCH_DSH_PATCH_EOF
chmod 600 "$LUMOSWITCH_DSH_PATCH"
env \
LUMOSWITCH_DEEPSEEK_API_KEY="{{access_key}}" \
npx @deepseek-ai/dsh web --patch "$LUMOSWITCH_DSH_PATCH"
)The terminal prints the local Web UI URL, http://127.0.0.1:3080 by default. Choose the current workspace after opening it. Harness still initializes its normal profile, stores sessions, and uses the npm cache; only the Lumoswitch overlay and process-scoped Key are guaranteed to disappear on exit.
Persistent use
Run the complete command during initial setup and whenever the Lumoswitch URL, Access Key, or models change. It writes a dedicated native DSH home and optional launcher, backing up an existing copy with timestamped 0600 permissions before updating it.
set -e
LUMOSWITCH_ROOT="$HOME/.config/lumoswitch/agents/deepseek-harness"
LUMOSWITCH_ENV="$LUMOSWITCH_ROOT/env.sh"
LUMOSWITCH_LAUNCHER="$HOME/.local/bin/lumoswitch-deepseek-harness"
mkdir -p "$LUMOSWITCH_ROOT" "$(dirname "$LUMOSWITCH_LAUNCHER")"
umask 077
if [ -f "$LUMOSWITCH_LAUNCHER" ]; then
LUMOSWITCH_BACKUP="$LUMOSWITCH_LAUNCHER.backup.$(date +%Y%m%d-%H%M%S)-$$"
cp "$LUMOSWITCH_LAUNCHER" "$LUMOSWITCH_BACKUP"
chmod 600 "$LUMOSWITCH_BACKUP"
fi
LUMOSWITCH_DSH_HOME="$LUMOSWITCH_ROOT/dsh-home"
mkdir -p "$LUMOSWITCH_DSH_HOME"
cat > "$LUMOSWITCH_DSH_HOME/cordis.patch.yml" <<'LUMOSWITCH_CONFIG_EOF'
- id: agent-default-model
config:
provider: lumoswitch
model: "{{model}}"
- id: llm-pi-ai
config:
providers:
lumoswitch:
displayName: Lumoswitch
apiKeyEnv: LUMOSWITCH_DEEPSEEK_API_KEY
api: openai-completions
baseURL: "{{api_base_url}}"
models:
{{deepseek_harness_models_yaml}}LUMOSWITCH_CONFIG_EOF
cat > "$LUMOSWITCH_DSH_HOME/.env" <<'LUMOSWITCH_ENV_EOF'
LUMOSWITCH_DEEPSEEK_API_KEY={{access_key}}
LUMOSWITCH_ENV_EOF
cat > "$LUMOSWITCH_ENV" <<'LUMOSWITCH_ENV_EOF'
export DSH_HOME="$HOME/.config/lumoswitch/agents/deepseek-harness/dsh-home"
LUMOSWITCH_ENV_EOF
case "$SHELL" in
*/zsh) LUMOSWITCH_PROFILE="$HOME/.zshrc" ;;
*/bash) LUMOSWITCH_PROFILE="$HOME/.bashrc" ;;
*) LUMOSWITCH_PROFILE="$HOME/.profile" ;;
esac
touch "$LUMOSWITCH_PROFILE"
LUMOSWITCH_PROFILE_MARKER='# Lumoswitch Agent: deepseek-harness'
LUMOSWITCH_SOURCE_LINE='[ -f "$HOME/.config/lumoswitch/agents/deepseek-harness/env.sh" ] && . "$HOME/.config/lumoswitch/agents/deepseek-harness/env.sh"'
if ! grep -Fqx "$LUMOSWITCH_SOURCE_LINE" "$LUMOSWITCH_PROFILE"; then
printf '\n%s\n%s\n' "$LUMOSWITCH_PROFILE_MARKER" "$LUMOSWITCH_SOURCE_LINE" >> "$LUMOSWITCH_PROFILE"
fi
printf '%s\n' "$LUMOSWITCH_PROFILE" > "$LUMOSWITCH_ROOT/profile-path"
cat > "$LUMOSWITCH_LAUNCHER" <<'LUMOSWITCH_LAUNCHER_EOF'
#!/usr/bin/env bash
set -e
LUMOSWITCH_ROOT="$HOME/.config/lumoswitch/agents/deepseek-harness"
LUMOSWITCH_ENV="$LUMOSWITCH_ROOT/env.sh"
LUMOSWITCH_LAUNCHER="$HOME/.local/bin/lumoswitch-deepseek-harness"
if [ "$1" = "--lumoswitch-clean" ]; then
if [ -f "$LUMOSWITCH_ROOT/profile-path" ]; then
LUMOSWITCH_PROFILE="$(cat "$LUMOSWITCH_ROOT/profile-path")"
if [ -f "$LUMOSWITCH_PROFILE" ]; then
LUMOSWITCH_PROFILE_MARKER='# Lumoswitch Agent: deepseek-harness'
LUMOSWITCH_SOURCE_LINE='[ -f "$HOME/.config/lumoswitch/agents/deepseek-harness/env.sh" ] && . "$HOME/.config/lumoswitch/agents/deepseek-harness/env.sh"'
LUMOSWITCH_PROFILE_TMP="$LUMOSWITCH_PROFILE.lumoswitch.$$"
awk -v marker="$LUMOSWITCH_PROFILE_MARKER" -v source="$LUMOSWITCH_SOURCE_LINE" '$0 != marker && $0 != source' "$LUMOSWITCH_PROFILE" > "$LUMOSWITCH_PROFILE_TMP"
mv "$LUMOSWITCH_PROFILE_TMP" "$LUMOSWITCH_PROFILE"
fi
fi
if [ -d "$LUMOSWITCH_ROOT" ]; then
find "$LUMOSWITCH_ROOT" -depth \( -type f -o -type l \) -delete
find "$LUMOSWITCH_ROOT" -depth -type d -exec rmdir {} \; 2>/dev/null || true
fi
find "$(dirname "$LUMOSWITCH_LAUNCHER")" -maxdepth 1 -type f -name 'lumoswitch-deepseek-harness.backup.*' -delete
rm -f "$LUMOSWITCH_LAUNCHER"
printf '%s\n' 'Lumoswitch native setup and optional launcher removed. Open a new terminal to refresh the environment.'
exit 0
fi
if [ -f "$LUMOSWITCH_ENV" ]; then . "$LUMOSWITCH_ENV"; fi
exec npx @deepseek-ai/dsh web "$@"
LUMOSWITCH_LAUNCHER_EOF
chmod 600 "$LUMOSWITCH_ENV"
find "$LUMOSWITCH_ROOT" -type f -exec chmod 600 {} \;
chmod 700 "$LUMOSWITCH_LAUNCHER"
. "$LUMOSWITCH_ENV"
printf '%s\n' 'Native Lumoswitch setup saved. Future command: npx @deepseek-ai/dsh web'
printf '%s\n' 'Optional maintenance launcher: ~/.local/bin/lumoswitch-deepseek-harness'
npx @deepseek-ai/dsh webFor everyday use, run:
npx @deepseek-ai/dsh webThe invoking directory becomes the default workspace location. Web arguments remain available, for example npx @deepseek-ai/dsh web --port 3081.
Native Windows PowerShell import
These commands run natively in Windows PowerShell without WSL and use the same connection values as the macOS/Linux templates on this page.
One-time use on Windows
& {
$ErrorActionPreference = 'Stop'
$lumoswitchConfig = Join-Path ([IO.Path]::GetTempPath()) ('lumoswitch-' + [Guid]::NewGuid().ToString('N') + '.yml')
$lumoswitchConfigContent = @'
- id: agent-default-model
config:
provider: lumoswitch
model: "{{model}}"
- id: llm-pi-ai
config:
providers:
lumoswitch:
displayName: Lumoswitch
apiKeyEnv: LUMOSWITCH_DEEPSEEK_API_KEY
api: openai-completions
baseURL: "{{api_base_url}}"
models:
{{deepseek_harness_models_yaml}}
'@
[IO.File]::WriteAllText($lumoswitchConfig, $lumoswitchConfigContent, [Text.UTF8Encoding]::new($false))
$lumoswitchEnvironmentNames = @('LUMOSWITCH_DEEPSEEK_API_KEY')
$lumoswitchPreviousEnvironment = @{}
foreach ($lumoswitchName in $lumoswitchEnvironmentNames) {
$lumoswitchPreviousEnvironment[$lumoswitchName] = [Environment]::GetEnvironmentVariable($lumoswitchName, 'Process')
}
$lumoswitchExitCode = 0
try {
[Environment]::SetEnvironmentVariable('LUMOSWITCH_DEEPSEEK_API_KEY', '{{access_key}}', 'Process')
$lumoswitchArguments = @('@deepseek-ai/dsh', 'web', '--patch', $lumoswitchConfig)
$lumoswitchExecutable = Get-Command 'npx.cmd' -CommandType Application -ErrorAction SilentlyContinue
if ($null -eq $lumoswitchExecutable) { $lumoswitchExecutable = Get-Command 'npx' -CommandType Application -ErrorAction Stop }
& $lumoswitchExecutable.Path @lumoswitchArguments
if ($null -ne $LASTEXITCODE) { $lumoswitchExitCode = $LASTEXITCODE }
} finally {
foreach ($lumoswitchName in $lumoswitchEnvironmentNames) {
[Environment]::SetEnvironmentVariable($lumoswitchName, $lumoswitchPreviousEnvironment[$lumoswitchName], 'Process')
}
Remove-Item -LiteralPath $lumoswitchConfig -Force -ErrorAction SilentlyContinue
}
if ($lumoswitchExitCode -ne 0) { throw 'npx exited with code ' + $lumoswitchExitCode }
}Persistent use on Windows
This command backs up existing Lumoswitch-owned files and restricts access to the configuration and launcher for the current user.
& {
$ErrorActionPreference = 'Stop'
$lumoswitchRoot = Join-Path $env:LOCALAPPDATA 'Lumoswitch\agents\deepseek-harness'
$lumoswitchLauncher = Join-Path $env:LOCALAPPDATA 'Lumoswitch\bin\lumoswitch-deepseek-harness.ps1'
$lumoswitchBackupSuffix = (Get-Date -Format 'yyyyMMdd-HHmmss') + '-' + $PID
function Protect-LumoswitchFile([string] $Path) {
$lumoswitchSid = [Security.Principal.WindowsIdentity]::GetCurrent().User.Value
& icacls.exe $Path '/inheritance:r' '/grant:r' ('*' + $lumoswitchSid + ':(F)') '*S-1-5-18:(F)' '*S-1-5-32-544:(F)' | Out-Null
if ($LASTEXITCODE -ne 0) { throw "Could not restrict access to $Path" }
}
function Install-LumoswitchFile([string] $Path, [string] $Content) {
[IO.Directory]::CreateDirectory((Split-Path -Parent $Path)) | Out-Null
if (Test-Path -LiteralPath $Path) {
$lumoswitchItem = Get-Item -LiteralPath $Path -Force
if ($lumoswitchItem.PSIsContainer -or ($lumoswitchItem.Attributes -band [IO.FileAttributes]::ReparsePoint)) {
throw "Lumoswitch refused to replace a directory or link: $Path"
}
$lumoswitchBackup = $Path + '.backup.' + $lumoswitchBackupSuffix
Copy-Item -LiteralPath $Path -Destination $lumoswitchBackup
Protect-LumoswitchFile $lumoswitchBackup
}
$lumoswitchTemporary = $Path + '.' + [Guid]::NewGuid().ToString('N') + '.tmp'
try {
[IO.File]::WriteAllText($lumoswitchTemporary, $Content, [Text.UTF8Encoding]::new($false))
Move-Item -LiteralPath $lumoswitchTemporary -Destination $Path -Force
Protect-LumoswitchFile $Path
} finally {
Remove-Item -LiteralPath $lumoswitchTemporary -Force -ErrorAction SilentlyContinue
}
}
[IO.Directory]::CreateDirectory($lumoswitchRoot) | Out-Null
$lumoswitchDshHome = Join-Path $lumoswitchRoot 'dsh-home'
$lumoswitchPatch = Join-Path $lumoswitchDshHome 'cordis.patch.yml'
$lumoswitchEnv = Join-Path $lumoswitchDshHome '.env'
$lumoswitchPatchContent = @'
- id: agent-default-model
config:
provider: lumoswitch
model: "{{model}}"
- id: llm-pi-ai
config:
providers:
lumoswitch:
displayName: Lumoswitch
apiKeyEnv: LUMOSWITCH_DEEPSEEK_API_KEY
api: openai-completions
baseURL: "{{api_base_url}}"
models:
{{deepseek_harness_models_yaml}}
'@
Install-LumoswitchFile $lumoswitchPatch $lumoswitchPatchContent
Install-LumoswitchFile $lumoswitchEnv 'LUMOSWITCH_DEEPSEEK_API_KEY={{access_key}}'
$lumoswitchEnvironment = [ordered]@{}
$lumoswitchEnvironment['DSH_HOME'] = (Join-Path $lumoswitchRoot 'dsh-home')
foreach ($lumoswitchEntry in $lumoswitchEnvironment.GetEnumerator()) {
[Environment]::SetEnvironmentVariable($lumoswitchEntry.Key, $lumoswitchEntry.Value, 'User')
[Environment]::SetEnvironmentVariable($lumoswitchEntry.Key, $lumoswitchEntry.Value, 'Process')
}
$lumoswitchLauncherContent = @'
param([Parameter(ValueFromRemainingArguments = $true)][string[]] $AgentArgs)
$ErrorActionPreference = 'Stop'
$lumoswitchRoot = Join-Path $env:LOCALAPPDATA 'Lumoswitch\agents\deepseek-harness'
$lumoswitchLauncher = Join-Path $env:LOCALAPPDATA 'Lumoswitch\bin\lumoswitch-deepseek-harness.ps1'
function Remove-LumoswitchFile([string] $Path) {
if (-not (Test-Path -LiteralPath $Path)) { return }
$lumoswitchItem = Get-Item -LiteralPath $Path -Force
if ($lumoswitchItem.PSIsContainer -or ($lumoswitchItem.Attributes -band [IO.FileAttributes]::ReparsePoint)) {
throw "Lumoswitch refused to remove a directory or link: $Path"
}
Remove-Item -LiteralPath $Path -Force
}
if ($AgentArgs.Count -gt 0 -and $AgentArgs[0] -eq '--lumoswitch-clean') {
foreach ($lumoswitchName in @('DSH_HOME')) {
[Environment]::SetEnvironmentVariable($lumoswitchName, $null, 'User')
[Environment]::SetEnvironmentVariable($lumoswitchName, $null, 'Process')
}
if (Test-Path -LiteralPath $lumoswitchRoot -PathType Container) {
$lumoswitchRootItem = Get-Item -LiteralPath $lumoswitchRoot -Force
if ($lumoswitchRootItem.Attributes -band [IO.FileAttributes]::ReparsePoint) { throw 'Lumoswitch refused to clean through a linked directory.' }
foreach ($lumoswitchFile in @(Get-ChildItem -LiteralPath $lumoswitchRoot -File -Recurse -Force)) { Remove-LumoswitchFile $lumoswitchFile.FullName }
foreach ($lumoswitchDirectory in @(Get-ChildItem -LiteralPath $lumoswitchRoot -Directory -Recurse -Force | Sort-Object FullName -Descending)) {
if ($null -eq (Get-ChildItem -LiteralPath $lumoswitchDirectory.FullName -Force | Select-Object -First 1)) { Remove-Item -LiteralPath $lumoswitchDirectory.FullName -Force }
}
if ($null -eq (Get-ChildItem -LiteralPath $lumoswitchRoot -Force | Select-Object -First 1)) { Remove-Item -LiteralPath $lumoswitchRoot -Force }
}
$lumoswitchBin = Split-Path -Parent $lumoswitchLauncher
if (Test-Path -LiteralPath $lumoswitchBin -PathType Container) {
foreach ($lumoswitchBackup in @(Get-ChildItem -LiteralPath $lumoswitchBin -Filter 'lumoswitch-deepseek-harness.ps1.backup.*' -File -Force)) { Remove-LumoswitchFile $lumoswitchBackup.FullName }
}
Remove-LumoswitchFile $lumoswitchLauncher
Write-Host 'Lumoswitch native setup and optional launcher removed. Open a new terminal to refresh the environment.'
exit 0
}
$lumoswitchEnvironment = [ordered]@{}
$lumoswitchEnvironment['DSH_HOME'] = (Join-Path $lumoswitchRoot 'dsh-home')
foreach ($lumoswitchEntry in $lumoswitchEnvironment.GetEnumerator()) {
[Environment]::SetEnvironmentVariable($lumoswitchEntry.Key, $lumoswitchEntry.Value, 'Process')
}
$lumoswitchExecutable = Get-Command 'npx.cmd' -CommandType Application -ErrorAction SilentlyContinue
if ($null -eq $lumoswitchExecutable) { $lumoswitchExecutable = Get-Command 'npx' -CommandType Application -ErrorAction Stop }
$lumoswitchArguments = @('@deepseek-ai/dsh', 'web')
& $lumoswitchExecutable.Path @lumoswitchArguments @AgentArgs
if ($null -ne $LASTEXITCODE) { exit $LASTEXITCODE }
'@
Install-LumoswitchFile $lumoswitchLauncher $lumoswitchLauncherContent
Write-Host 'Native Lumoswitch setup saved. Future command: npx @deepseek-ai/dsh web'
Write-Host 'Optional maintenance launcher:' $lumoswitchLauncher
$lumoswitchExecutable = Get-Command 'npx.cmd' -CommandType Application -ErrorAction SilentlyContinue
if ($null -eq $lumoswitchExecutable) { $lumoswitchExecutable = Get-Command 'npx' -CommandType Application -ErrorAction Stop }
$lumoswitchArguments = @('@deepseek-ai/dsh', 'web')
& $lumoswitchExecutable.Path @lumoswitchArguments
if ($null -ne $LASTEXITCODE -and $LASTEXITCODE -ne 0) { throw 'npx exited with code ' + $LASTEXITCODE }
}The import writes the Agent-supported persistent configuration and starts npx @deepseek-ai/dsh web directly. The saved Lumoswitch maintenance script is not required to start the Agent; run it with --lumoswitch-clean when you need to restore or remove this setup.
Remove the Lumoswitch setup
Windows PowerShell
Exit every Lumoswitch session for this Agent, then run the command below. The optional maintenance launcher removes the managed native configuration, restores any preserved shared file, clears Lumoswitch user environment values, and removes itself.
& {
$lumoswitchLauncher = Join-Path $env:LOCALAPPDATA 'Lumoswitch\bin\lumoswitch-deepseek-harness.ps1'
if (-not (Test-Path -LiteralPath $lumoswitchLauncher -PathType Leaf)) {
throw 'The optional Lumoswitch maintenance launcher is missing. Run persistent import again before cleanup.'
}
& powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -File $lumoswitchLauncher --lumoswitch-clean
}macOS / Linux
The command below removes only the Agent-supported persistent configuration and optional Lumoswitch launcher, overlay, and their backups. It does not delete Harness profiles, settings, workspaces, or sessions:
"$HOME/.local/bin/lumoswitch-deepseek-harness" --lumoswitch-cleanRotate that Access Key in Lumoswitch afterward.
Verify and troubleshoot
- The model picker should show the
Lumoswitchprovider and every downstream model in the API configuration. - A default already saved in
$DSH_HOME/settings.yamltakes precedence over the overlay's initial default. Select a Lumoswitch model in the Web UI in that case. - A
401response usually means the Access Key is stale or contains extra whitespace. - A
404response usually means/v1is missing or duplicated in the Base URL. - If the model list is stale, copy and run the persistent command again to regenerate the dedicated overlay; the shared Harness settings do not need editing.
- DeepSeek Harness remains in Developer Preview. If an upgrade reports a configuration-schema error, compare the command with the current provider and CLI references.
References: DeepSeek Harness repository, model-provider configuration, and dsh CLI behavior.
Maintainer publication fields
- Platform ID:
deepseek-harness - Display name:
DeepSeek Harness - Downstream protocol:
openai-compatible commandTemplate: copy the complete One-time launch code block- Persistent strategy:
native persistentCommandTemplate: copy the complete setup command under Persistent usefutureCommand:npx @deepseek-ai/dsh web- Display order:
35 - Last verified:
2026-08-27