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 asmcp__serena__*; - adds a
serena:guideprompt 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-filereadcalls that never touches a serena tool, nudging the model back tomcp__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
serenaCLI onPATH(install viauv tool install serena-agentper Serena's docs). -
A serena context for this plugin. A context without
single_projectexposes multi-project activation; the plugin uses--projectper session, so a multi-project context is required. Serena's context schema requires apromptfield (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_direxcluded_toolshides the serena tools that duplicate the harness built-ins (read_file→read,find_file→glob,execute_shell_command→bash,create_text_file→write,list_dir).promptis a Jinja2 template serena renders into its Instructions Manual, which the model reads throughinitial_instructions; keep it short — the plugin's ownserena:guidesection carries the full preference rules.
Mount
-
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.) -
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 -
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 themcp__serena__*tools and the guide section.
Other profiles work the same way with --profile <name>.
Config
| Field | Default | Meaning |
|---|---|---|
serverName | serena | MCP server name; tool prefix becomes mcp__<serverName>__* |
command | serena | Executable starting the serena MCP server |
context | dsh | Serena context name or path (a user-installed multi-project context) |
env | — | Extra env for the spawned serena process |
toolCallTimeoutMs | 60000 | Per callTool timeout |
startupTimeoutMs | 15000 | Bounded wait for the initial serena connect before the first request |
failOnStartupError | false | Reject 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'sstartConnectionspawns the serena process and registers its tools on the agent's scope (mcp__serena__*).--project <cwd>pins the session's workspace. - system-prompt seam —
ctx.systemPrompt.section()registers the guide at order 110, rendered only while themcp__serena__find_symbolsentinel is visible. - tools seam —
ctx.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:
startConnectionregisters 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_symbolpresence check turns both mechanisms on or off per agent. If the serena server registers under a differentserverName, 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
readpasses every time, and after a deny the model can use built-ins freely for two minutes. This mirrors Serena's ownremindhook and avoids blocking legitimate use, but it does not stop a model that is merely slow to switch.