DSH WorkBuddy Connect
corrinehu/dsh-workbuddy-connect将 WorkBuddy 桌面 App 包含的模型自动接入 DeepSeek Harness,在 DSH 对话窗口里零配置使用。
把本机 TinyTitan 模型服务接入 Harness:按已安装模型刷新 llm-pi-ai 路由,并挂载一个不做思考的压缩后端。
A DeepSeek Harness bundle for running models from a local TinyTitan server. It is deliberately thin: the harness's own llm-pi-ai adapter serves these models, so this package adds configuration, not a protocol implementation.
Two jobs, both at boot, both idempotent:
tools/dsh_route.sh in this checkout turns theinstalled models under models/ into the llm-pi-ai route block — served ids, each template's thinking levels, and the three switches that are easy to get wrong by hand (thinkingFormat: chat-template, the keyless-route auth header, the long stream idle timeout). The plugin applies that block through the harness's own settings service, so the change lands in the active profile patch and hot-reloads; the model picker follows models/ instead of a copy someone typed once. A plugin installed from a catalogue is a plain package beside no checkout, so there is no script to run: only then the same block is generated in-process from the server's own catalog (generate.js), and the log says so. Wherever tools/dsh_route.sh exists it stays the source of truth, so a checkout user has one implementation, not two.
titles are marked purpose: "compaction" / "session-title" and name no reasoning level, so the harness fills in the route's default. On a local thinking model that spends a summariser's own output cap on thinking and costs tens of seconds on the title of every new session. The plugin registers an agent preset built from the harness's shipped standard composition with that row pointed at dsh-tinytitan/backend, which forces thinking off for those calls only — ordinary turns keep the route's level. While your profile has selected no preset of its own, this one becomes the default; an explicit choice on the Agent presets page is never overwritten.
This bundle supports exactly one DeepSeek Harness release: `0.2.1-rc.2` — not older, not newer, and not a build from main. Both jobs above are written against that release: the preset is generated from _its_ shipped standard composition (the preset-standard row of @deepseek-ai/dsh-web-app/presets/standard.patch.yml, read through @deepseek-ai/dsh-agent-preset's agentPresets.register), so the row ids move when the harness does, and the compaction backend subclasses that release's dsh-compaction-basic. It is also the release tools/dsh_local.sh installs.
The pin lives in four places that cannot import each other — package.json's peerDependencies (exact, no range), the launcher's DSH_VERSION default, SUPPORTED_DSH_VERSION in src/support.js, and the bundle's own `version`, which is the release it supports rather than a number of its own — and test/support.test.js fails if any of them disagrees.
On any other release — older, newer, or a build from main — the plugin refuses to run. It writes one line to stderr naming both versions and does nothing else: no route, no preset, no watcher, and no writes into your harness home. It never throws, so DSH boots normally, every other plugin loads, and removing this plugin leaves nothing to undo. A version that cannot be read at all is refused the same way, because a plugin that writes into the harness home has no business proceeding on a harness it cannot identify.
stderr is not a style choice. The harness collects a plugin's log records and prints them only when the boot itself fails, and its startup exporter takes level ≥ 2, so a refusal logged through the host logger is invisible on a healthy boot — which would be indistinguishable from a plugin that silently stopped working. Found by booting a throwaway harness, not by reading.
dsh plugin --profile web add file:/path/to/TinyTitan/plugins/dsh-tinytitanRestart DSH (or start a new session) and the plugin logs what it did. It writes through the harness's own services, so the changes are part of your profile configuration and hot-reload with it:
What · Change the llm-pi-ai entry · the route block, refreshed from the catalog the agent-preset registry · a tinytitan preset, built from the harness's shipped standard composition with the compaction row on this backend the selected agent preset · set to tinytitan only while the profile names no selection of its own
There is no settings file to edit and no preset file to copy: DSH 0.2.0 removed both, and the plugin uses the replacements. If you would rather select the preset yourself, set setDefaultWhenUnset: false — then the plugin registers tinytitan and leaves your choice alone.
If you remove the plugin, set another preset on the Agent presets page first: the selection it made is a normal setting, and nothing is left behind to clean it up.
A file: install is a copy, not a link: after editing this package, re-install it (dsh plugin --profile web remove dsh-tinytitan then add again) or DSH keeps running the copy it made.
The preset keeps its id (tinytitan), so sessions that were already on it keep working — the id is what a session records, and only its declaration changed from a generated file to a registry entry. Three things are worth knowing:
tinytitan only while nothing else is selected. If you had picked another preset, open the Agent presets page and pick TinyTitan to get the quiet compaction back; a choice you make there is never overwritten.
~/.dsh/.agent-presets/tinytitan/ (the 0.1.6 generated preset) is inert — 0.2.0does not read that directory. Deleting it is optional cleanup.
~/.dsh/settings.yaml.imported is your old settings file, moved there by theharness when it imported it into the profile patch. Keep it; it is the record of what was migrated.
Every field is optional; these are the defaults the cordis.patch.yml row writes out, and TINYTITAN_PORT / TINYTITAN_REASONING / TINYTITAN_CONTEXT / TINYTITAN_MAX_TOKENS / TINYTITAN_REPO / TINYTITAN_SERVER / TINYTITAN_MODELS_DIR / DSH_HOME are the environment fallbacks.
The three switches that start work nobody asked for — autoGoal (a goal from every prompt), autonomy (the fresh-agent ralph loop) and handoff (a child session when the window's budget is spent) — are written out as false in that row. Each one changes what every prompt in a profile means, so a profile opts in by naming the field with true; installing the bundle never does it for you.
Field · Default · Meaning port · resolved: config.port, else TINYTITAN_PORT, else 8080 · the port the TinyTitan server serves on. Nothing in this bundle pins it, so the environment can point the route at a server on another port provider · tinytitan · the llm-pi-ai provider route name reasoning · resolved: config.reasoning, else TINYTITAN_REASONING, else medium · the route's declared default reasoning level. It must match how the server was started: a route that says "think" against a server running --reasoning off makes a dense Qwen spend its whole output budget inside the reasoning block and never answer presetId · tinytitan · the agent preset this plugin registers context · unset (the writer's 262144) · the contextWindow the route declares, for the provider and every model row ($TINYTITAN_CONTEXT). Unset keeps the block byte-for-byte what the launcher's pin writes; naming one is how a narrowed route survives the next refresh. A positive whole number of tokens, or the row is refused at boot maxTokens · unset (the writer's 32768) · the output cap the same route declares ($TINYTITAN_MAX_TOKENS), with the same unset-means-pin and validation rules as context above registerRoute · true · refresh the route block from tools/dsh_route.sh watchModels · true · keep watching models/ and refresh when an install appears or disappears watchDebounceMs · 2000 · how long the folder has to be quiet before the refresh runs selfContained · false · use the built-in generator even where tools/dsh_route.sh exists serverBinary · discovered · the TinyTitanServer the built-in generator runs ($TINYTITAN_SERVER) modelsDir · /models · the installs it describes ($TINYTITAN_MODELS_DIR) writeCompactionPreset · true · register the tinytitan preset setDefaultWhenUnset · true · select that preset only while the profile has selected none compactionHeadroomTokens · unset (the harness's 65536) · the compaction engine's headroom in the generated preset — see below autoGoal · false · arm a goal from every direct human prompt, so the harness keeps working until the model completes it — the /goal behaviour without typing /goal autoGoalRounds · 99999 · the round cap an auto-created goal gets. Effectively unbounded, so an unattended run is not stopped by a small budget; set a lower number to bound prompts a person did not mark as long-running autonomy · false · the preset half of "work it until it is done": append the autonomy policy to the preset's persona, so the model chooses the best technical solution instead of asking, and mount the fresh-agent ralph loop — enabling the shipped row, or appending the harness's ralph bundle when the standard ships none — see below autonomyRounds · 64 · the maxRounds the enabled ralph row carries; the harness enforces its own ceiling on a call override autonomySuppressQuestions · false · also remove the ask_user_question tool from the preset, so the model cannot ask. Off by default because plan mode's own instructions use that tool handoff · false · continue an unfinished objective in a fresh child context before the session's window fills, so a task survives its own context limit — see below handoffHops · 3 · how many times one goal may be handed on before the chain stops and is left to a person handoffAtTokens · unset (auto: 60% of the routed model's window, held 128K above its end — 131072 on 256K, 600000 on a 1,000,000-token route, 629145 on 1M) · the prompt-token count at which the next turn hands the objective on. A positive integer pins one number; unset follows the context window the LLM route declares, which is what keeps the hop below compaction on every window size handoffWindowRatio · 0.6 · the fraction of that window the auto budget may use. The compaction trigger is a different fraction per shape (~0.625 of a 256K window, 0.8 of a 1M one), so a ratio has to clear the narrowest shape; above that, compaction continues the session in place and the handoff mostly only fires on the max-tokens wall handoffMaxChildren · 8 · how many handoff children may be alive at once in the process, ancestors included; has to sit above handoffHops repoRoot · this checkout · where tools/dsh_route.sh lives dshHome · $DSH_HOME or ~/.dsh · the harness home, for the built-in generator's file fallback
The built-in generator looks for the server at serverBinary, then TINYTITAN_SERVER, then TinyTitanServer on PATH, then the checkout's release build; it looks for models at modelsDir, then TINYTITAN_MODELS_DIR, then /models. On a harness with no settings service it falls back to editing the home's settings.yaml — the 0.1.6 shape — and refuses to write when that file does not exist; through the service it makes no write at all when the refresh would not change a byte.
0.1.6's adoptDefaultPreset (re-point the current default preset's stock compaction row) has no counterpart here and the field is gone: presets are declared rows now, so adopting a shipped one would mean freezing its whole plugin list in your patch — the drift the generated preset exists to avoid. Select the tinytitan preset instead; it is the default whenever you have chosen nothing.
The compaction trigger is why `compactionHeadroomTokens` exists. The engine compacts at min(window × thresholdRatio, window − maxTokens − headroomTokens). The harness's default headroom is 65,536 tokens, and the generated preset leaves that default alone, so on the route this project declares (262,144 window, 32,768 cap) the trigger sits at ~62% of the window rather than the documented 80%. That is a safe direction — less context, less KV on a local server — but on a _narrow_ declared window the arithmetic flips: below roughly cap + 65,536 the pressure budget goes negative, the engine logs one warning and then never compacts, and the session eventually fails on the context wall. So narrow the window in the row, not by hand: tools/dsh_route.sh --context writes a route the plugin's next refresh — boot, or a change under models/ — puts straight back, unless the same numbers are in the row as context and maxTokens. Then set compactionHeadroomTokens too, e.g. 0 to let the ratio govern again or the cap's quarter (8192) to keep a guard: both leave a positive budget at every window where window > maxTokens.
With no server built, the folder is read directly. The catalog normally comes from TinyTitanServer --catalog, which is the authority on what an install is. A profile that has installed models but has not built the server yet has nothing to ask, so src/catalog-scan.js walks models/ itself — the same rules, mirroring `Mod