Back to home@FomoGoMan

dsh-serena-guide

A DeepSeek Harness plugin that steers agents to Serena's LSP symbolic tools over built-in code tools.

Stars
1
Language
TypeScript
Created
Sep 2, 2026
Updated
Sep 2, 2026

Introduction

dsh-serena-guide

A plugin for DeepSeek Harness that makes its agents prefer Serena's LSP-backed symbolic tools over the built-in Read/Glob/Grep/Edit for code work.

For every agent it:

  • spawns a serena MCP server bound to that agent's working directory (serena start-mcp-server --context dsh --project <cwd>), registering its tools on the agent's scope as mcp__serena__*;
  • adds a serena:guide prompt section that declares Serena tools PRIMARY and the built-in code tools SECONDARY for code files, superseding the built-in tool guidance where they conflict;
  • adds a deny guard that blocks a burst of built-in glob/grep/code-file read calls that never touches a serena tool, nudging the model back to mcp__serena__*.

The project binding is deterministic — a process argument, not a model instruction — so the active project is always the session's workspace and the model never has to call activate_project.

Serena is an MCP server exposing language-server-backed, symbol-aware tools (find_symbol, rename_symbol, replace_symbol_body, get_symbols_overview, …). You must have the serena CLI installed and have a multi-project serena context (see Requirements).

Requirements

  • A DeepSeek Harness installation (the harness CLI loads this plugin from a profile). The @deepseek-ai/* peer packages are provided by the host installation — do not install them yourself.

  • The serena CLI on PATH (install via uv tool install serena-agent per Serena's docs).

  • A serena context for this plugin. A context without single_project exposes multi-project activation; the plugin uses --project per session, so a multi-project context is required. Serena's context schema requires a prompt field (there is no default — a file without it fails to load), so the context file needs all of the following. Example ~/.serena/contexts/dsh.yml:

    description: DeepSeek Harness agent context (per-session --project binding)
    prompt: |
      You have access to Serena's symbolic code-intelligence tools
      (mcp__serena__*), bound to your current working directory. Prefer them
      over built-in grep/read for code work.
    excluded_tools:
      - create_text_file
      - read_file
      - execute_shell_command
      - find_file
      - list_dir
    

    excluded_tools hides the serena tools that duplicate the harness built-ins (read_fileread, find_fileglob, execute_shell_commandbash, create_text_filewrite, list_dir). prompt is a Jinja2 template serena renders into its Instructions Manual, which the model reads through initial_instructions; keep it short — the plugin's own serena:guide section carries the full preference rules.

Mount

  1. Install the package into your profile:

    dsh plugin --profile web add dsh-serena-guide
    

    (Until the harness releases and the package is on npm, install from this checkout instead: dsh plugin --profile web add /path/to/dsh-serena-guide.)

  2. Mount it in the profile's patch layer — add to ~/.dsh/profiles/web/cordis.patch.yml:

    - insert:
        - id: serena-guide
          name: dsh-serena-guide
          config:
            context: dsh      # your multi-project serena context
    
  3. Restart the profile (dsh --profile web). When an agent's first turn starts, a serena server is spawned for that session's workspace; the first prompt already carries the mcp__serena__* tools and the guide section.

Other profiles work the same way with --profile <name>.

Config

FieldDefaultMeaning
serverNameserenaMCP server name; tool prefix becomes mcp__<serverName>__*
commandserenaExecutable starting the serena MCP server
contextdshSerena context name or path (a user-installed multi-project context)
envExtra env for the spawned serena process
toolCallTimeoutMs60000Per callTool timeout
startupTimeoutMs15000Bounded wait for the initial serena connect before the first request
failOnStartupErrorfalseReject the agent install when the initial serena connection fails

Model Experience

The tool-preference section

The serena:guide section at order 110 (the tool-guidance band, after the persona at 0 and the file-reference context at 99, before tool-bash at 105 and the SDK section at 150), registered per agent and present only while that agent's serena tools are visible. The startup gate puts it there from the first prompt: the session's first assemble waits up to startupTimeoutMs for serena to connect and re-assembles rather than racing the spawn and arriving guide-less, so the model reads the preference before its first tool choice. It declares Serena tools PRIMARY and built-in Read/Glob/Grep/Edit SECONDARY for code files, states that conflicting built-in tool instructions are SUPERSEDED, gives a task→tool mapping table, the required pre-edit workflow, the allowed exceptions, and a self-check before every built-in tool call. The model sees nothing from this section when no serena tool is registered.

The deny guard

A ctx.tools.guard registered per agent that returns a deny reason once the run of built-in glob, grep, or code-file read calls since the last serena call or deny crosses the threshold (three calls). The tools runtime surfaces the reason as a blocked tool result — Error: You were using many grep/glob/read calls without Serena's symbolic tools. Consider using mcp__serena__find_symbol / get_symbols_overview instead… — which the model reads and reacts to. A serena call resets the run; a deny resets it too and no second deny fires within two minutes. Non-tracked tools (edit, write, bash, …) and non-code read targets are neutral.

Token effect

Conditional and fixed-length. Zero tokens while serena tools are absent; a stable ~30-line block while present. No unbounded or agent-dependent content. The deny produces a tool result only when it fires, never on an allowed call.

KV Cache effect

Prefix-stable while its presence does not change: static prose, identical across turns and agents, so it joins the shared prefix without per-turn drift. Toggling serena on or off at profile re-composition changes whether the whole section exists, which invalidates the prefix from this point — a composition-time event, not a per-turn one.

How it works

Serena is an external stdio MCP server. The plugin is the bridge that rides three of the harness's extension seams:

  • MCP seam@deepseek-ai/dsh-mcp-client's startConnection spawns the serena process and registers its tools on the agent's scope (mcp__serena__*). --project <cwd> pins the session's workspace.
  • system-prompt seamctx.systemPrompt.section() registers the guide at order 110, rendered only while the mcp__serena__find_symbol sentinel is visible.
  • tools seamctx.tools.guard() denies the glob/grep/read bursts.

Because the harness assembles the first prompt synchronously right after turn/start (before a serena spawn could finish), the plugin registers on the agent context synchronously (not through a deferred ctx.inject fiber) and gates the first system-prompt/assemble on the serena connect, re-assembling so the first request already carries serena. A failed or timed-out connect falls through to a plain built-in-tools session.

Development

The plugin imports @deepseek-ai/* packages that are not yet published; package.json lists them as peerDependencies (fulfilled by a host harness installation) and devDependencies that link: to a local ../deepseek-harness checkout for typechecking and tests. With the harness checkout at that path:

pnpm install
pnpm run typecheck
pnpm run test
pnpm run build

Known Limitations and Deferred Work

  • Per-agent serena instance — every agent spawns its own serena process bound to its working directory; two sessions on different workspaces are fully isolated, at the cost of one serena startup (~1–3 s, which the session's first request waits for) per session. Sharing one instance per workspace is deferred: startConnection registers tools on the ctx it is given, so a shared connection cannot serve multiple agent scopes with the current API.
  • Whole-section gating on one sentinel — a single mcp__<serverName>__find_symbol presence check turns both mechanisms on or off per agent. If the serena server registers under a different serverName, the section stays silent and the guard stays inert.
  • Enforcement is run-scoped, not absolute — the guard denies only after three glob/grep/read calls since the last serena call; a single call, a pair, or a non-code read passes every time, and after a deny the model can use built-ins freely for two minutes. This mirrors Serena's own remind hook and avoids blocking legitimate use, but it does not stop a model that is merely slow to switch.