跪拜 Guibai
← Back to the summary

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:

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:

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.