<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>DeepSeek Harness on Trevor's Blog</title><link>http://hitrevor.com/en/tags/deepseek-harness/</link><description>Recent content in DeepSeek Harness on Trevor's Blog</description><generator>Hugo</generator><language>en-US</language><lastBuildDate>Sat, 14 Nov 2026 17:06:15 +0800</lastBuildDate><atom:link href="http://hitrevor.com/en/tags/deepseek-harness/index.xml" rel="self" type="application/rss+xml"/><item><title>DeepSeek Opened the App's APIs. Harness Learned to Import History.</title><link>http://hitrevor.com/en/posts/dsh-api-import/</link><pubDate>Sat, 14 Nov 2026 17:06:15 +0800</pubDate><guid>http://hitrevor.com/en/posts/dsh-api-import/</guid><description>DeepSeek opened the App&amp;rsquo;s toolkit as API parameters: search, file parsing, speech. One sentence from me triggered three server-side searches, billed per tool call. Same night, dsh 0.2.2-rc.3 added chat import, and upgrading a profile disabled six plugins.</description><content:encoded><![CDATA[<p>On November 13, DeepSeek did two things that point in opposite directions.</p>
<p>The official release opened up the &ldquo;whole-suite app API.&rdquo; Web search, file parsing, speech — capabilities that until then existed only inside the App — now work from the API by declaring a parameter. The pitch line is &ldquo;reproduce the complete DeepSeek App, 1:1.&rdquo;</p>
<p>That same evening, DeepSeek Harness shipped v0.2.2-rc.3. It added exactly one thing: import the conversation history sitting in the DeepSeek App into your workspace and keep talking.</p>
<p>One side pushes capability out. The other pulls data in. Neither is big news alone; together they say something. The vendor is disassembling its own App while the community stitches its own history back together.</p>
<p>I ran both on my machine. Some numbers don&rsquo;t match the docs.</p>
<p><img alt="Two directions on the same day" loading="lazy" src="/images/dsh-api-import/fig1.png"></p>
<h2 id="first-what-they-actually-opened">First, What They Actually Opened</h2>
<p>What got opened is really two layers: parameter switches, and standalone endpoints.</p>
<p>Layer one is switches. In the existing Messages, Responses and Chat Completions APIs, declare the tool and it works: <code>deepseek_web_search</code> handles web search and deep-thinking search, <code>deepseek_read_url</code> reads a link, and file input and model safety each get a parameter.</p>
<p>The key is one line of documentation: search and fetching both happen server-side, the client executes no tools.</p>
<p>That line decides how you use it. You don&rsquo;t write your own scraper and wire it to the model; you send one request and the server walks the middle steps for you.</p>
<p>Layer two is standalone endpoints. Search, Fetch, OCR, ASR and TTS can each be called on their own, without going through a chat model. Take that route and you are the one assembling the workflow.</p>
<p>Together, the official positioning is &ldquo;turn the parameters on and you have the App.&rdquo; I checked how much of that holds up.</p>
<h2 id="one-call-three-searches-on-the-server">One Call, Three Searches on the Server</h2>
<p>I tried the simplest possible request against the Responses API: ask when Kotlin 2.5.0-RC ships and what&rsquo;s in it, answer in three sentences.</p>
<p>The request body had three fields: model <code>deepseek-flash</code>, tool <code>deepseek_web_search</code>, one line of input.</p>
<p>The response came back at 6,969 bytes in 4.2 seconds. Not one answer — five content blocks in sequence: reasoning, search call, reasoning, search call, answer.</p>
<p>The first search block says <code>kind: &quot;deep_search&quot;</code>. The model issued three queries on its own, mixing Chinese and English. One asked about the release date and features in Chinese, one used <code>site:kotlinlang.org</code> to pin results to the official site, and one went straight for the release announcement page.</p>
<p>Those three queries brought back 16 sources, each with url, title, snippet, site_name and published_at. I counted: official kotlinlang.org doc pages sitting alongside reposts on third-party forums.</p>
<p>That&rsquo;s the real cost of &ldquo;the server walks it for you.&rdquo; You say one sentence; it searches three times behind your back.</p>
<p><img alt="Content blocks and usage from a single request" loading="lazy" src="/images/dsh-api-import/fig2.png"></p>
<h2 id="you-pay-per-tool-execution">You Pay Per Tool Execution</h2>
<p>One field deserves its own section: <code>server_tool_use</code>. This call returned tool_search 3, tool_fetch 0, tool_image_search 0. Three searches, no fetches, no reverse image search.</p>
<p>The same usage block carries <code>input_tokens: 11799</code> (4,608 of them cache hits) and <code>output_tokens: 761</code> (433 of them reasoning tokens).</p>
<p>Of those eleven thousand input tokens, the overwhelming majority is the content of those 16 sources, not your question.</p>
<p>So when the docs say &ldquo;tool call fees are charged on top of token fees,&rdquo; the thing being counted is <code>server_tool_use</code>. You write one line of tool declaration in your code, the bill grows three lines of search fees, and you don&rsquo;t control how many times it searches.</p>
<h2 id="five-standalone-endpoints-tried-one-by-one">Five Standalone Endpoints, Tried One by One</h2>
<p>If your use case doesn&rsquo;t need conversation, calling the standalone endpoints is cheaper. I ran the three I could verify locally.</p>
<p>TTS was the cleanest. Send a Chinese paragraph plus a voice_id, get a wav back in 2.09 seconds. The default voice stella returns 276,524 bytes, 5.76 seconds, 24kHz; switching to tide gives 261,164 bytes, 5.44 seconds. Same text, male voice 0.32 seconds shorter than female.</p>
<p>ASR closed the loop on that wav. 1.2 seconds to return, <code>duration_ms: 5760</code>, matching the 5.76 seconds afinfo measured locally.</p>
<p>The transcript has a trap, though. What I said was &ldquo;多字段的 value class 在字节码里就是一个普通类，没有 box-impl&rdquo; — a multi-field value class is just an ordinary class in bytecode, no box-impl. What came back was &ldquo;多字段的Value Class在字节码里就是一个普通类，没有Box应用.&rdquo;</p>
<p><code>box-impl</code> came back as &ldquo;Box应用&rdquo; — Box app.</p>
<p>This isn&rsquo;t an ASR-specific defect. Anyone dropping English technical terms into Chinese speech recognition hits it: the model hears the sound and guesses the closest Chinese word. Write code comments by voice and that error walks straight into the repo.</p>
<p>For OCR I used a cover image I&rsquo;d made myself: 2.29 seconds, 481 input tokens for 131 output tokens. Layout like &ldquo;36.0 字节 / 次&rdquo; (36.0 bytes per call) and &ldquo;0 / 2 迁移开关&rdquo; (0 of 2 migration switches) — decimal points, slashes and all — came back accurate, right down to the middle-dot separator.</p>
<p><img alt="Measured numbers for the three standalone endpoints" loading="lazy" src="/images/dsh-api-import/fig3.png"></p>
<h2 id="harness-added-exactly-one-thing">Harness Added Exactly One Thing</h2>
<p>That same evening dsh v0.2.2-rc.3 landed, commit <code>38a6a5c</code>, by imccyu and winewill.</p>
<p>Two changes: a new experimental &ldquo;DeepSeek chat import&rdquo; plugin, and a fix for in-app updates failing on the Windows desktop build.</p>
<p>The second is a plain bug fix, nothing to unpack. The first is worth discussing, because it pairs with the API released that day: the official demo supports importing past conversations, and dsh turned that into a plugin.</p>
<p>The plugin is an optional layer — the release notes state plainly that shipped profiles don&rsquo;t include it. You mount it by hand:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">insert</span><span class="p">:</span><span class="w">  </span><span class="c"># appended to the end of the profile&#39;s patch layer</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l">deepseek-chat-import</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;@deepseek-ai/dsh-deepseek-chat-import&#39;</span><span class="w">
</span></span></span></code></pre></div><p>Usage: click the upload arrow next to &ldquo;New session,&rdquo; pick a <code>deepseek_data-*.zip</code>, then pick the workspace that should receive the conversations.</p>
<h2 id="the-upgrade-hit-a-wall">The Upgrade Hit a Wall</h2>
<p>I upgraded my everyday web profile first. <code>npm install -g @deepseek-ai/dsh@0.2.2-rc.3</code>, it installed 0.2.2-rc.3, I restarted.</p>
<p>The startup log flooded a screen:</p>
<pre tabindex="0"><code>disabling profile plugin row &#34;storage-domain&#34;: Plugin
@deepseek-ai/dsh-storage-domain@0.2.2-rc.1 is incompatible with dsh 0.2.2-rc.3
</code></pre><p>storage-domain, plugin-market, cua, ui-cua, tool-plugin-market, cua-provider-registry — all six disabled. Same reason every time: the profile has rc.1, the main package is rc.3, the versions don&rsquo;t line up.</p>
<p>Then the chain reaction: storageDomain can&rsquo;t start, so workspaceRegistry goes pending, session-controller follows it into pending, and the plugin I actually care about sits in the list waiting on workspaceRegistry.</p>
<p>Nothing was broken about the plugin. It was held up by upstream. This is the other face of the &ldquo;Everything is a Plugin&rdquo; philosophy: the finer the composition, the further one version mismatch cascades through the whole stack.</p>
<p><img alt="Six plugins disabled for version mismatch" loading="lazy" src="/images/dsh-api-import/fig5.png"></p>
<h2 id="a-clean-profile-and-it-runs">A Clean Profile and It Runs</h2>
<p>Rather than touch the dependencies of the profile I use daily, I spun up a fresh one from the official template: <code>dsh --profile chatimport --from-default-profile web --port 3081</code>, a different port so it wouldn&rsquo;t collide with the original.</p>
<p>The new profile carries just two bundles, <code>@deepseek-ai/dsh-base</code> and <code>@deepseek-ai/dsh-web-app</code>. Clean. Add that insert block, restart, and the log shows no disabling lines and nothing pending.</p>
<p>The UI gained a button. I dumped every button on the page through the accessibility tree; the second one is called Import DeepSeek chats, 38 pixels square, right next to New session.</p>
<p>So the plugin is alive. The question becomes what to feed it.</p>
<h2 id="import-test-three-two-one-skipped">Import Test: Three, Two, One Skipped</h2>
<p>I don&rsquo;t have a real export package, so I reverse-engineered the structure from the zod schema in the plugin source and built one.</p>
<p>A valid export ZIP needs three things: <code>manifest.json</code> with <code>export_format_version: 3</code>, a <code>chat_session_index.json</code> listing sessions, and one <code>chat_sessions/{id}.json</code> per session. The session file holds a message tree, each message carrying id, parent_id, role, status and fragments.</p>
<p>I built three sessions: a plain Q&amp;A, one with web search and multi-round reasoning, and one designed to test filtering.</p>
<p>In the first version I made &ldquo;content filtered&rdquo; mean message status <code>FINISHED</code> with a <code>CONTENT_FILTER</code> fragment stuffed in. The result dialog said, flatly: Imported 3 sessions into the smoke-test-connectivity-f0b666d1b2bb workspace.</p>
<p>All three came through, including the one that should have been dropped. I went into the conversion source and found the check reads exactly one field: <code>if (reply?.status === 'CONTENT_FILTER')</code> then <code>continue</code>, with the counter increment as its own separate step.</p>
<p>The filter looks at the message&rsquo;s own status, not the fragment type. I fixed that and rebuilt the package; the second dialog said Imported 2 sessions, followed by 1 conversation could not be imported.</p>
<p>Two in, one skipped. That matches the docs: &ldquo;a question whose answer was withdrawn by content filtering is omitted along with the answer, and sessions with no other turn are not imported.&rdquo;</p>
<p>Which confirmed something else: import the same package twice and the sessions duplicate. My workspace sidebar now shows five sessions, with the Kotlin one and the Flutter one each having two copies, and the filtered one still single. The importer keeps no record of what it has already imported.</p>
<p><img alt="The import funnel and the duplicates" loading="lazy" src="/images/dsh-api-import/fig4.png"></p>
<h2 id="import-writes-locally-only-chatting-touches-the-model">Import Writes Locally; Only Chatting Touches the Model</h2>
<p>Opening an imported session, the history is complete: my questions, my answers, the timestamps, rendered as 11/10 17:00, with 1 turns 1 steps at the bottom.</p>
<p>Then I sent a new question straight into that session to see whether it could keep talking. I got a red line: This turn failed, saying the deepseek-official provider route has no API key, to write one through the credentials service or export one in the startup environment, with a MISSING_CREDENTIAL code at the end.</p>
<p>The new profile has no credentials configured — that&rsquo;s my environment. But the error itself proves something: import never called the model at any point, or it would have stalled here first.</p>
<p>The red &ldquo;Injected context&rdquo; line in the UI says the same thing: the history is already in context, there&rsquo;s just nobody reading it. In the screenshot, that failed turn pushed the bottom stats to 2 turns 2 steps.</p>
<h2 id="two-roads-two-different-problems">Two Roads, Two Different Problems</h2>
<p>Put the two side by side and they answer different questions.</p>
<p>The whole-suite API answers &ldquo;how do I build something that behaves like the DeepSeek App.&rdquo; It wraps the dirty work — search, parsing, speech — and sells it to you: token fees plus tool fees, in exchange for not building your own retrieval and parsing.</p>
<p>Chat import answers &ldquo;how do I stop losing what I&rsquo;ve already talked through.&rdquo; It has nothing to do with the model — pure local read and write, splitting a ZIP into session files and writing them into a workspace.</p>
<p>On my machine the first produces a result in four seconds, the second puts three sessions into the sidebar in one. But the first asks you to trust that its server searched three times on your behalf, and the second asks you to accept that importing the same package five times will write five copies.</p>
<p>Two things shipped the same day: one pushing capability out, one pulling history in. If you have to pick one to start with, it comes down to whether you&rsquo;re short on capability or short on memory.</p>
]]></content:encoded></item><item><title>DeepSeek Harness Code Review: The Bottleneck Was Never Git</title><link>http://hitrevor.com/en/posts/dsh-worktree-review/</link><pubDate>Thu, 12 Nov 2026 09:20:00 +0800</pubDate><guid>http://hitrevor.com/en/posts/dsh-worktree-review/</guid><description>DeepSeek Harness 0.2.2-rc.2 added review by branch, commit and file type. I measured every stage on a 21,711-file repository: creating a worktree takes 1.5 seconds, producing a 28,458-line diff takes 75 milliseconds, and what actually does not fit is 300,000 tokens of context.</description><content:encoded><![CDATA[<p>DeepSeek Harness shipped 0.2.2-rc.2, and one line in the notes said Git Worktree now supports reviewing changes by branch, commit and file type.</p>
<p>I went to upgrade that same night. npm did not have that version at all.</p>
<h2 id="three-places-three-version-numbers">Three places, three version numbers</h2>
<p>GitHub Releases had dsh-v0.2.2-rc.2, published at 11:33 on the 11th. I tried to install it and npm returned notarget.</p>
<p>A quick look at dist-tags: both latest and next still pointed at 0.2.2-rc.1.</p>
<p><img alt="Three places, three version numbers" loading="lazy" src="/images/dsh-worktree-review/fig1.png"></p>
<p>The desktop app was older still, sitting on 0.2.1-rc.2. So my machine was holding three version numbers at once and none of them was rc.2.</p>
<p>That is not a DeepSeek Harness flaw, every release process behaves this way. But it decides when you can actually use a new feature: either wait for npm to catch up, or switch the desktop app to the dev update channel, which is itself something rc.2 introduced.</p>
<h2 id="so-i-reproduced-it-myself">So I reproduced it myself</h2>
<p>Since I could not install it, I reproduced what DeepSeek Harness does on my own machine.</p>
<p>Worktrees, comparing by branch, slicing by file type: all of that is git commands underneath and has nothing to do with the client. I used my own repository, 21,711 files and 60,037 commits, as the target, with a 50-file repository alongside as a control.</p>
<p>Starting with the slowest step: creating a worktree.</p>
<p><img alt="Worktree creation time" loading="lazy" src="/images/dsh-worktree-review/fig2.png"></p>
<p>Small repository: 32 milliseconds, three runs at 31, 32, 32.</p>
<p>Large repository: 1.5 seconds, three runs at 1.47, 1.49, 1.51. Rock steady. A 46x gap.</p>
<p>The checked-out worktree occupies 275 MB, but <code>.git</code> is shared, so the 840 MB repository was not copied again. That design saves a lot of disk.</p>
<p>1.5 seconds is not long, but if an agent creates a worktree on every edit, with network round trips, disk jitter and a pile of invisible waiting mixed in, that 1.5 seconds becomes a bet that could time out at any moment. The release notes say &ldquo;relaxed the wait time for creating worktrees in large repositories,&rdquo; and this 1.5 seconds is what that refers to.</p>
<h2 id="three-review-dimensions-all-in-milliseconds">Three review dimensions, all in milliseconds</h2>
<p>I made two rounds of changes inside the worktree to simulate an agent at work: 40 files in the first round, 240 in the second, totaling 311 files and 28,458 lines against the main branch.</p>
<p>Then I compared along the three dimensions it describes.</p>
<p><img alt="Timing across the three review dimensions" loading="lazy" src="/images/dsh-worktree-review/fig3.png"></p>
<p>Stats by branch: 65 milliseconds. Full body: 75. Grouped by file type: 29. Log by commit: 26.</p>
<p>All four are under a hundred milliseconds. Nothing at the git layer is slow.</p>
<p>Here is the counterintuitive part. A 28,458-line, 1.27 MB diff takes git only 75 milliseconds to compute. I expected it to dominate and it turned out to be the fastest thing in the room.</p>
<h2 id="pick-the-wrong-baseline-and-you-review-nothing">Pick the wrong baseline and you review nothing</h2>
<p>I stepped on something here worth writing down.</p>
<p>My first comparison used <code>git diff main...HEAD</code>. Three dots means comparing against the merge base of two branches. It printed zero files.</p>
<p>The reason is simple: local main on that repository was far behind, so HEAD was itself an ancestor of main. The merge base resolved to HEAD, and comparing a commit against itself is of course empty.</p>
<p>Switching to <code>origin/main...HEAD</code> immediately gave 311 files.</p>
<p>Same repository, same branch, only the baseline changed, and it went from 0 to 311 files.</p>
<p>So the thing to be careful about with review-by-branch is not whether diff is fast, it is what the diff is comparing against. If the agent&rsquo;s branch was cut from an old baseline, you will see considerably less than was actually changed, and nothing will warn you.</p>
<p>Three dots compare merge bases, two dots compare endpoints. That distinction rarely matters until review is automated, at which point it becomes the difference between seeing the change and not seeing it at all.</p>
<h2 id="how-to-read-those-28458-lines">How to read those 28,458 lines</h2>
<p>Since git is not slow, the slowness must be elsewhere. Split the diff by extension and the answer surfaces.</p>
<p><img alt="Change distribution by file type" loading="lazy" src="/images/dsh-worktree-review/fig4.png"></p>
<p>Python: 187 files, 12,468 lines, 43%. JSON: 52 files, 5,690 lines, 20%. TypeScript: 46 files, 2,647 lines. TSX: 12 files, 1,526 lines.</p>
<p>The interesting part is those 5,690 JSON lines. Lock files, contract snapshots, config exports, a fifth of the total line count, and under five percent of it needs to be read line by line.</p>
<p>The value of review-by-file-type is not tidier categorization. It is having a principled reason to skip an entire 5,690-line folder.</p>
<h2 id="what-a-worktree-saves-is-not-disk">What a worktree saves is not disk</h2>
<p>I assumed worktrees would eat disk space. Measured, that worry was unfounded.</p>
<p>Against 21,711 files and an 840 MB <code>.git</code>, the worktree occupies only 275 MB, because <code>.git</code> is shared and only the working tree gets copied out.</p>
<p>What those 275 MB buy is isolation: one repository can host several worktrees, each on a different branch, without interfering. Agent A edits code on feat-a while agent B edits elsewhere on feat-b, and each side has its own working tree and index.</p>
<p>A sandbox or a full repository clone costs you 840 MB of disk plus tens of seconds of copying. A worktree costs 1.5 seconds, and throws in branch isolation for free.</p>
<p>That is also why review is built on top of worktrees. The branch is there, the commits are there, the files are there, and whichever dimension you slice by, the raw material is already in place.</p>
<h2 id="the-bottleneck-is-context-not-tooling">The bottleneck is context, not tooling</h2>
<p>Put every stage&rsquo;s number side by side and the conclusion is clear.</p>
<p><img alt="The magnitude of each stage" loading="lazy" src="/images/dsh-worktree-review/fig5.png"></p>
<p>Creating a worktree: 1.5 seconds. Producing the diff: 75 milliseconds.</p>
<p>Hand that diff to a model and it is 1,268,059 characters, roughly 300,000 tokens.</p>
<p>What is 300,000 tokens? Mainstream models now carry context windows around 200,000, so this single change does not fit, before counting repository structure, the task description and your own question.</p>
<p>So when rc.2 says &ldquo;reduced jank when viewing large changes,&rdquo; what it is janking on is not git. It is the layer that renders this pile, feeds it to a model and puts it in front of a human. No amount of optimizing those millisecond figures helps, because the real ceiling is here.</p>
<p>Review by file type, by commit, by branch: three paths that are three ways of cutting. Cut it down far enough and only then can a model finish reading.</p>
<h2 id="the-one-i-could-not-test">The one I could not test</h2>
<p>rc.2 added two more things: named and multi-level SubAgent collaboration, and AgentTeam that can be enabled per session with shared members and a task panel.</p>
<p>I cannot reproduce that locally, because it is not a git-layer concern, it is orchestration. And rc.2 is not installable from npm, so all I have is rc.1, and running that would not show rc.2 behavior.</p>
<p>So I only write what I verified. That one is for people who can actually get their hands on it.</p>
<h2 id="what-you-can-do-now">What you can do now</h2>
<p>If your agent already works on branches, two things are worth changing today. First, give the worktree directory a fast disk. Most of that 1.5 seconds is disk I/O, and a spinning disk or a network volume makes it worse.</p>
<p>Second, read the stats before the body. 312 lines versus 28,458 is a 90x difference, and reading the former first can filter out half the files you do not need to open.</p>
<p>As for DeepSeek Harness rc.2, it is a matter of waiting for npm. Once you have it, the first thing worth doing is running worktree creation against your largest repository to see whether your machine takes 1.5 seconds or 15.</p>
<p>That number decides whether you let it branch freely, and whether your pipeline can run several agents editing the same codebase in parallel. A 1.5-versus-15-second difference on one machine ends up being the difference between &ldquo;three agents at once&rdquo; and &ldquo;one at a time,&rdquo; two entirely different architectural choices.</p>
]]></content:encoded></item><item><title>After Upgrading DeepSeek Harness, All My Plugins Got Disabled</title><link>http://hitrevor.com/en/posts/dsh-022/</link><pubDate>Thu, 29 Oct 2026 07:52:35 +0800</pubDate><guid>http://hitrevor.com/en/posts/dsh-022/</guid><description>The version on npm is not the newest one. You have to pin the version number to get it. Once installed, three plugins were rejected on the spot. Not a bug. An exact-version lock doing its job.</description><content:encoded><![CDATA[<p>Last night I upgraded DeepSeek Harness on my machine from 0.2.1-rc.2 to 0.2.2-alpha.2. Sixteen seconds.</p>
<p>One thing has to be said up front. This version is not the latest one on npm yet. A plain install still gives you 0.2.1-rc.2, so you have to pin the version number by hand.</p>
<pre tabindex="0"><code>npm install -g @deepseek-ai/dsh@0.2.2-alpha.2
</code></pre><p>Skip that version number and nothing I describe below will be visible to you.</p>
<p>Then I started it, and there was the problem. Three consecutive <code>disabling profile plugin row</code> lines in the log. Three of the things I had installed were taken away.</p>
<p>npm had also flagged something during the install: install scripts for five packages were blocked, including two native modules, node-pty and koffi. I stared at it for a while and started the app anyway. The web UI opened, the model list loaded, nothing seemed broken. I filed that away to look at later if something misbehaved.</p>
<h2 id="three-lines-hit-me-the-moment-it-booted">Three lines hit me the moment it booted</h2>
<p>Here is the log, verbatim.</p>
<pre tabindex="0"><code>dsh: disabling profile plugin row &#34;plugin-market&#34;:
  Plugin @deepseek-ai/dsh-plugin-market@0.2.1-rc.2
  is incompatible with dsh 0.2.2-alpha.2:
  requirements {&#34;@deepseek-ai/dsh-plugin-manager&#34;:&#34;0.2.1-rc.2&#34;,
                &#34;@deepseek-ai/dsh-settings&#34;:&#34;0.2.1-rc.2&#34;,
                &#34;@deepseek-ai/dsh-tools&#34;:&#34;0.2.1-rc.2&#34;,
                &#34;@deepseek-ai/dsh-typert-protocol&#34;:&#34;0.2.1-rc.2&#34;}
</code></pre><p>The three that got disabled were <code>ui-agent-team</code>, <code>plugin-market</code>, and <code>tool-plugin-market</code>. The last two are actually the same package, referenced by two different config rows.</p>
<p>The <code>requirements</code> block is the whole story. It reads <code>&quot;0.2.1-rc.2&quot;</code>. No caret, no tilde, four exact version numbers. And the four internal packages at runtime had already moved to 0.2.2-alpha.2.</p>
<p>Exact match fails, plugin gets rejected. DeepSeek Harness handles it cleanly: it does not load the plugin, it does not throw, it prints one warning line and keeps going.</p>
<p>That design is actually right. Letting a plugin that declared &ldquo;I was only tested against these versions&rdquo; run on a newer runtime makes the eventual crash far harder to diagnose. Rejection beats loading something broken.</p>
<p>The cost lands on the user though. You bump a version, things you installed disappear, and because it is a warning rather than an error, it is very easy to miss.</p>
<h2 id="the-fallout-spreads-to-the-plugin-market">The fallout spreads to the plugin market</h2>
<p>I had a custom plugin index configured. It lives at line 12 of <code>~/.dsh/profiles/web/cordis.patch.yml</code> and points at a local static server.</p>
<p>After the upgrade I opened Plugins → Discover and got one line in red:</p>
<blockquote>
<p>Could not read the index sources.</p>
</blockquote>
<p>There was a Retry button next to it. Clicking it returned the same line.</p>
<p><img loading="lazy" src="/images/dsh-022/05-discover-failed.png"></p>
<p>My first instinct was the static server. <code>lsof -iTCP:3489</code> came back empty, so the service had died at some point. I assumed that was the whole story, brought it back up, and <code>curl</code> returned 200 with a 3,764-byte index.json sitting there.</p>
<p>Back to the browser, clicked Retry. Same red line.</p>
<p>Same red line on the second try, and only then did it click: reading the index is itself an action performed by plugin-market. The index source was fine. The thing that reads it was gone. An empty page either way.</p>
<p>So there are two layers stacked here. On the surface the index source was down. Underneath, the plugin that reads it had been disabled.</p>
<p>I also tripped over a hole of my own making, worth noting. The <code>serve.py</code> I use for reproduction hardcodes its access log at <code>/tmp/market/access.log</code>, and I had cleaned that directory out. Every incoming request threw before it could respond, so <code>curl</code> returned exit 52 with an empty body. Creating the directory fixed the requests.</p>
<h2 id="there-is-an-official-switch-buried-in-the-warning-text">There is an official switch, buried in the warning text</h2>
<p>The tail of those three warnings does give you a way out. It mentions <code>dsh plugin allow-version</code>, or the plugin manager, to grant an exemption for an exact version.</p>
<p>I read the help first. The plugin subcommand documentation for DeepSeek Harness does not mention this at all. It only says the remaining arguments are forwarded to pnpm.</p>
<p>So I just ran it, and the usage fell out on its own:</p>
<pre tabindex="0"><code>dsh: Error: usage: dsh plugin allow-version &lt;package@version&gt;
                   --dsh-version &lt;exact&gt; --accept-risk
</code></pre><p>Filling that in, one exemption per plugin:</p>
<pre tabindex="0"><code>$ dsh plugin --profile web allow-version \
    &#34;@deepseek-ai/dsh-plugin-market@0.2.1-rc.2&#34; \
    --dsh-version 0.2.2-alpha.2 --accept-risk
dsh: allowed @deepseek-ai/dsh-plugin-market@0.2.1-rc.2 for DSH 0.2.2-alpha.2
</code></pre><p>The exemption is scoped to one package at one version. A different version and you have to grant it again. After restarting DeepSeek Harness, the three warning lines were gone from the log, Discover read the index immediately, both plugins listed properly, and category and tag filters were back.</p>
<p><img loading="lazy" src="/images/dsh-022/06-discover-fixed.png"></p>
<p>One note: <code>--accept-risk</code> means what it says. I got lucky here and the plugin ran without incident. That is not a guarantee.</p>
<h2 id="what-the-new-interface-looks-like">What the new interface looks like</h2>
<p><img loading="lazy" src="/images/dsh-022/02-plugins-page.png"></p>
<p>The plugins page now has two tabs, Installed and Discover. Installed splits into two sections.</p>
<p>The top section is Built-in plugin settings, four entries. Subagent governs recursion depth, count, and model. Web search configures the DeepSeek search source. Agent loop controls how the agent dispatches tool calls. Shell limits how long a single command may run and how much output it may produce.</p>
<p>The bottom section is Experimental plugins, five entries, each with a toggle:</p>
<p>Agent teams lets the agent split work across named teammates that share a task board and report results back.</p>
<p>Voice input transcribes locally with SenseVoice; the first use requires installing dependencies.</p>
<p>Custom plugin indexes is what adds the Discover tab to Plugins, letting you browse and install from index sources you add.</p>
<p>Auto authorization review is a new review mode: before each tool call the model checks it against your original request and batch-approves the ones that comply.</p>
<p>Change requests &amp; worktrees tracks each session&rsquo;s change requests along with their check and review results, and can start new sessions directly inside a Git worktree.</p>
<p>Of the five, the first two are on by default and the last three are off.</p>
<h2 id="two-things-that-actually-changed-on-the-plugins-page">Two things that actually changed on the plugins page</h2>
<p>After all that, it is worth looking back at what this upgrade actually delivered.</p>
<p>The plugin detail page now renders the package&rsquo;s own README. Click into an experimental plugin and you get the full documentation, with an English and Chinese toggle, a summary, a table of contents, and a section called Use this package further down.</p>
<p>More interesting is that the same page breaks the bundle into its 5 components, listed one per row, each with an icon, a one-line description, a short name, the full package name, and its own toggle on the right.</p>
<p>Take the Change requests &amp; worktrees package. Expanded, it is five parts.</p>
<p>Change requests, GitHub change requests, Session change requests, Worktree service, Change request panel. The top of the page reads <code>Components 5 total · 5 off</code>.</p>
<p>The bottom also shows provenance: Code source is Built in, current version 0.2.2-alpha.2.</p>
<p><img loading="lazy" src="/images/dsh-022/04-plugin-components.png"></p>
<p>This is far clearer than the old &ldquo;one plugin, one switch&rdquo; model. You can inspect each part of a bundle and flip them independently, and you no longer have to open a config file to find out what is installed.</p>
<h2 id="where-i-land-on-this">Where I land on this</h2>
<p>The interesting part of this whole episode is not the trap I fell into. It is the design tradeoff the trap exposes.</p>
<p>An exact-version lock keeps the plugin ecosystem safe, and the price is that any runtime upgrade can invalidate a batch of plugins at once. It is especially visible between alpha and rc: bump a few internal packages and every plugin declaring a dependency on them drops offline.</p>
<p>DeepSeek Harness provides an exemption switch, but the default path is rejection. The bet it is making is &ldquo;better to load less than to run something broken.&rdquo;</p>
<p>For people building plugins, this means reissuing a version declaration every time you follow a runtime release. For people using plugins, it means knowing what you installed before you upgrade, because afterwards you may not even be able to tell what went missing.</p>
<p>DeepSeek&rsquo;s official release notes for 0.2.2-alpha.2 also mention that a failed hot plugin update will attempt to restore the previous version. That mechanism protects updates already in flight. It cannot save the version check that runs before startup. The two pieces of logic do not touch each other.</p>
<p>This release also lists a few things I did not verify: subagent_oar can hand standalone tasks to a local Codex or Claude CLI, and the developer tools stats plugin can show context, cache, model, and tool timing by turn. The first one sounds the most interesting, but I could not find an entry point in the UI, so it likely needs a separate install and I left it alone. Same for the hot-update rollback: official wording, and I did not construct a failure to test it.</p>
<p>Do you check which plugins you have installed before upgrading your toolchain? Or are you like me, finding out only after things are gone.</p>
<h2 id="references">References</h2>
<ul>
<li>DeepSeek Harness v0.2.2-alpha.2 release notes: <a href="https://github.com/deepseek-ai/deepseek-harness/releases/tag/dsh-v0.2.2-alpha.2">https://github.com/deepseek-ai/deepseek-harness/releases/tag/dsh-v0.2.2-alpha.2</a></li>
<li>Test environment: macOS arm64, DeepSeek Harness upgraded directly from 0.2.1-rc.2 to 0.2.2-alpha.2, npm global install</li>
</ul>
]]></content:encoded></item></channel></rss>