dsh-set-session-title
A model-facing set_session_title tool for DeepSeek Harness (dsh) — the agent can rename its own session.
Why this exists
DSH owns session titles properly: they are log-only session/title events (zero tokens) served by @deepseek-ai/dsh-session-title, written either by a deterministic first-prompt fallback or by an optional LLM provider.
What is missing is a way for the agent to fix one. Automatic titling can produce a content-free result — observed on a real session: the provider overwrote a usable fallback with the bare phrase 会话标题 ("session title"), i.e. the instruction's own vocabulary echoed back (discussion #8289). The rename capability itself exists (SessionTitleService.rename(session, title), the GUI rename dialog, the host's own delivery path uses it), but 0.2.0-rc.2 exposes it to no model-facing surface:
- no tool;
- no
sessionCLI subcommand (pluginandduplicateonly); /apiguarded by an authority-bound signed cookie whose launch token is a per-processrandomBytes()value that is never persisted, so a local process cannot legitimately obtain it.
This plugin adds exactly that one existing service call as a tool. It does not touch authentication, storage, or the session log directly — the capability is granted by composing the row.
Behaviour
| Write | appends a session/title event with source: { kind: "user" } |
| Pinning | a user-sourced title is authoritative: in-flight automatic generation is superseded and later prompts schedule none (an explicit refresh is the deliberate unpin) |
| Scope | the calling agent's own session (exec.agent.session) |
| Normalization | trimmed, terminal control sequences removed, capped at the deployment's maxTitleBytes (dsh-base ships 80); input that normalizes to empty is rejected |
| Cost | changes no message in the conversation and adds no tokens |
Install
# recommended: git spec pinned to a release tag (reproducible, and immune to the tarball caveat below)
dsh plugin --profile <name> add github:CancerTiN/dsh-set-session-title#v0.1.1
# drop the #v0.1.1 fragment to follow the default branch instead of a pinned release
# a tarball URL works too — read the caveat below before picking one
dsh plugin --profile <name> add https://github.com/CancerTiN/dsh-set-session-title/archive/refs/tags/v0.1.1.tar.gz
The manifest declares dsh.bundle.patch, so add appends dsh-set-session-title to the profile's dsh.profile.bundles and its patch inserts the tool row into the composed tree.
Tarball-URL caveat. Under the pnpm bundled with the desktop app (11.7.0), a GitHub tarball URL installs once and then fails on every later resolve that reuses the same pnpm store —
ERR_PNPM_MISSING_TARBALL_INTEGRITY, where deleting the lockfile and--forceboth change nothing. Reproduced and reported upstream as discussion #8294. A git spec is unaffected because it resolves to a commit-addressedgitHostedentry, which pnpm does not subject to the integrity requirement; a tarball URL is still fine when the store has not seen it (or on a newer pnpm).
On 0.2.0-rc.2 the desktop app picked the tool up without a restart (the client-side tool schema updated mid-session and the call succeeded immediately). Other builds may need the profile restarted.
Requirements
Both injected services ship with dsh-base, so the default web / desktop / headless compositions satisfy them:
| service | role |
|---|---|
tools |
tool registry |
sessionTitle |
@deepseek-ai/dsh-session-title (row id session-title) |
A composition missing either one fails loud at load instead of registering a tool that cannot work.
Usage
Ask the agent, or call it from a skill. Argument:
{"title": "安装终端插件并核验连通性"}
Result:
{"title": "安装终端插件并核验连通性", "eventSeq": 1307}
Read back either way (both should show the new title):
# the projection cache the GUI reads; seq matches the returned eventSeq
python3 -c "import json;print(json.load(open('$DSH_HOME/storages/session_projcache/sessions/$DSH_SESSION_ID.json'))['record']['rows']['title'])"
# the durable log; the last session/title event's source.kind should be "user"
zstd -dc "$DSH_HOME"/sessions/*/"$DSH_SESSION_ID"/session.v4.jsonl.zstd | grep session/title | tail -1
Why this package has zero dependencies
This is a hard constraint, not a style choice.
Declaring an in-box package (for example @deepseek-ai/dsh-tools, the documented home of defineTool) as a dependency makes pnpm materialize a second copy inside the profile. Module identity then splits for a module-local Symbol that keys a runtime surface of the tool registry, so ctx.tools[TOOL_RUNTIME_SCHEDULER] reads back undefined inside dsh-agent-loop and every tool call in that profile fails — bash, read, and this plugin's tool alike — with a bare Cannot read properties of undefined (reading 'prepare'). Reproduced and isolated on 0.2.0-rc.2; written up in discussion #8287.
So lib/index.js imports nothing and the tool definition is hand-written JSON Schema. ctx.tools.register() requires only output: { schema, render } plus a schema inside the supported subset (type / properties / required / additionalProperties / items / enum / const and the description annotation) — which is what defineTool compiles a declaration into. The trade-off: register() does not wrap execute with schema validation, so the single argument is checked by hand.
Verified on
@deepseek-ai/dsh-desktop-runtime 0.2.0-rc.2, macOS, session format v4 — both a headless profile and the desktop app's live session:
{"type":"tool/call","seq":1306,"data":{"name":"set_session_title","arguments":"{\"title\":\"安装终端插件并核验连通性\"}"}}
{"type":"session/title","seq":1307,"data":{"title":"安装终端插件并核验连通性","messageSeqs":[],"source":{"kind":"user"}}}
messageSeqs: [] is the signature of rename(): the title is explicit, not derived from a prompt.
Limitations
- Only the calling session can be renamed; there is no cross-session rename and no target-id argument.
- A non-agent caller (plain host-side execution) is rejected.
- A deployment that does not compose
session-titlefails loudly at load — deliberate, so the tool can never appear without the capability behind it.
Uninstall
dsh plugin --profile <name> remove dsh-set-session-title
Titles already written stay in the session log: a title is a log fact, not plugin state.
License
MIT
No comments yet. Be the first to write one.