DeepSeek Harness Is a Runtime You Assemble, Not a Chatbot You Launch
DeepSeek Harness Hands-On: From Launching the Web UI to Assembling Your Own Agent
The DeepSeek Harness README is short. Running it directly requires just one command:
npx @deepseek-ai/dsh web
But if you only launch the page, fill in an API Key, and treat it as just another chat tool, you'll miss the most valuable part of this project.
This isn't a repeat of the documentation directory. Instead, it walks through a real usage path: launching, configuring models, selecting a workspace, understanding sessions, and then creating your own Profile.
Get the Project Running First
For a direct experience, you can use npx:
npx @deepseek-ai/dsh web
When you need to study the source code or develop plugins, the source-code approach is recommended:
git clone https://github.com/deepseek-ai/deepseek-harness.git
cd deepseek-harness
corepack enable
pnpm install
pnpm run build
pnpm dsh web
The current source code requires Node.js ^22.19.0 || >=24.0.0.
The service listens by default on:
http://127.0.0.1:3080
During my local build, the installation process warned that the Linux native package does not support macOS. This is a platform selection warning. As long as pnpm run build and pnpm dsh --help pass normally, it does not mean the installation failed.
Why the Input Box Is Disabled After the Page Opens
Harness uses the directory where the launch command was run as the default filesystem location, but the Web UI does not select it automatically.
You need to click "Select Workspace," add and select the project directory. Without a selected workspace, the input box remains disabled.
This design seems like an extra step, but it actually clarifies the Agent's filesystem scope. It won't allow a new session to directly operate on a directory just because the service was launched from it.
After selecting a workspace, you can first run a read-only task:
Summarize this repository and identify its main packages.
Confirm that reading, command execution, and the approval chain all work normally before proceeding to file-writing tasks.
Where the API Key Is Saved
After adding a DeepSeek API Key in "Settings → Models," the model configuration takes effect immediately without restarting the service.
Harness stores credentials and general configuration separately:
$DSH_HOME/.credentials.yaml
$DSH_HOME/settings.yaml
The former saves credentials; the latter saves Providers, models, and credential references.
After a successful save via the Web page, you only get back a desensitized description; the plaintext key is not retrieved again. This is more suitable for long-running operation than mixing the API Key into ordinary JSON configuration.
If using environment variables, you can also provide it when launching the Headless Agent:
DEEPSEEK_API_KEY=your_key \
pnpm dsh --profile headless "summarize this workspace"
In a real project, never write the Key into patches, repository configurations, or shell scripts.
Two Pitfalls When Configuring Custom Models
Harness supports directory Providers, as well as OpenAI-compatible gateways and self-built services.
A custom Provider typically requires:
llm-pi-ai:
providers:
my-gateway:
apiKeyEnv: GATEWAY_API_KEY
api: openai-completions
baseURL: https://gateway.example/v1
models:
- id: my-model
The first pitfall is the Provider ID.
The Provider ID is written into session logs, default models, and credential references. Once in use, it is not suitable for in-place renaming. The correct approach is to add a new Provider, then migrate and delete the old configuration.
The second pitfall is multimodal capability.
Manually added models are treated as text-only models by default. If a model supports images, you need to declare this explicitly:
models:
- id: vision-model
input: [text, image]
This is only a declaration of the endpoint's capability; it does not verify whether the server actually supports images. If declared incorrectly, the Provider will ultimately reject the request.
Moreover, when an image has already entered the session log, simply modifying the configuration may not solve the problem. Subsequent requests might continue to derive the same attachment from the log. In this case, you should start a new session that does not contain that image.
Web and Headless Are Not Two Separate Products
Web mode is suitable for interactive use:
pnpm dsh web
Headless mode is suitable for automated tasks:
pnpm dsh --profile headless "inspect the repository"
Both share the same underlying capabilities; they just load different Bundles.
The Web Profile adds a server, frontend, and interaction capabilities; the Headless Profile adds a one-shot task runner, requiring no browser or server.
This is also the core purpose of Profiles: the same set of capabilities can be assembled into different product forms.
Don't Modify Source Code Directly; Learn to Patch First
Suppose we have a local plugin:
import type { Context } from '@deepseek-ai/cordis'
export const name = 'hello-plugin'
export function apply() {
console.log('hello plugin loaded')
}
You can create a cordis.yml:
- insert:
- id: hello
name: '/absolute/path/to/my-plugin.ts'
Then overlay it onto the Web Profile:
pnpm dsh web --patch ./scratch-plugin/cordis.yml
This is more suitable for experimentation than directly modifying the built-in Bundle, because the Patch only takes effect for this launch.
To view the final result:
pnpm dsh --profile web --dump-config
--dump-config is very important. When Profiles, Home configurations, and multiple Patches coexist, don't try to deduce the final configuration in your head; directly inspect the assembled result.
Create Your Own Profile
If a certain set of plugins needs to be used long-term, you can create an independent Profile:
dsh plugin --profile review add your-review-bundle
dsh --profile review --dump-config
dsh --profile review
The Profile will be saved at:
$DSH_HOME/profiles/review/
├── package.json
└── cordis.patch.yml
package.json records dependencies and Bundle order; cordis.patch.yml saves the current Profile's local overrides.
To remove a plugin:
dsh plugin --profile review remove your-review-bundle
This allows you to maintain multiple Agents for different purposes:
web daily interaction
headless automated tasks
review read-only code review
writer documentation and content processing
sandbox restricted command execution
True permission isolation still relies on the filesystem, processes, and Sandbox Provider, not just on Profile names.
A Session Is Not a Set of Chat Messages
Harness writes Turns, Steps, model outputs, and Tool calls as session events.
This affects several daily operations:
- Changing the default model mainly affects new sessions
- Sessions that have already sent requests retain their own model records
- Tool Calls and results can be replayed
- Sessions can be resumed or forked
- The UI and model context come from the same log
So when encountering strange historical behavior, don't just look at the current Settings; also consider what has already been recorded in the session log.
Where Permission Control Happens
After the model generates a Tool Call, it does not directly touch the filesystem or processes:
Model outputs Tool Call
→ tools/pre-execute
→ Permissions and Approval
→ Tool execute
→ Filesystem or Process Provider
→ tools/post-execute
→ Write to session log
This chain is far more reliable than "telling the model in the system prompt not to delete files recklessly."
I further recommend splitting Profiles by purpose:
- Provide only read capability when reading code
- Open write access when modifying code
- Authorize Shell and network separately
- Use a Sandbox Provider for untrusted repositories
- Lock third-party plugin versions or Commit SHAs
A model's self-discipline is not a security boundary; Providers and policies are.
Common Command Quick Reference
# Launch Web UI
pnpm dsh web
# View parameters
pnpm dsh web --help
# View the final plugin tree
pnpm dsh --profile web --dump-config
# Temporarily load a Patch
pnpm dsh web --patch ./extra.cordis.yml
# Run a one-shot Headless task
pnpm dsh --profile headless "inspect this repository"
# Install a Bundle
dsh plugin --profile demo add <package>
# Remove a Bundle
dsh plugin --profile demo remove <package>
The key to getting started with DeepSeek Harness is not memorizing how many commands there are, but establishing this understanding:
What you launch is not a fixed Agent, but a runtime assembled from Profiles, Bundles, Plugins, and Patches.
Once you understand this, subsequent plugin development will go smoothly.