DSH plugin user commands: DeepSeek Harness ctx.commands

Plugin DevelopmentPublished 2026-10-07Author: DeepSeek Plugin Market
DSH pluginDeepSeek Harnessuser commandsctx.commandsslash commands
DSH plugin slash commands: how to register a user command, why the handler runs without the model, and the log-only command/run lifecycle.

To give users a slash command, a DSH plugin goes to a registry entirely separate from tools: the user command service provided by dsh-commands lets interactive adapters discover plugin-owned commands and execute them against the exact agent, creating no model message (source). This article covers the CommandDefinition field rules, the log-only command lifecycle, scoped shadowing, and the ctx.commands API; event dispatch modes are in DSH plugin events, and tool registration and contracts are in the tool execute contract.

CommandDefinition in a DSH plugin: what to fill in to register a user command

CommandDefinition is the plugin-owned registration definition, and the registry validates and freezes an effective definition detached from the original registered object (source). The fields:

  1. name — a lowercase command name without the leading slash. To offer /plan, register plan.
  2. description — a human-readable summary used in discovery UI.
  3. input (optional) — a CommandInputDescriptor that currently carries only hint: a placeholder shown before the user supplies free-form input, advertised to capable clients. Command availability is decided by plugin composition — every adapter consuming the registry sees all effective definitions.
  4. recordInput (optional, defaults to true) — whether command/run records rawInput. When the command's domain event already owns that payload, set it to false to avoid duplicating the payload in the session log.
  5. handler — (invocation: CommandInvocation) => CommandResult | Promise<CommandResult>, which executes against the receiving agent without sending the command to the model.

Invocation and results in a DSH plugin user command: direct execution without the model

Cancellation is the adapter's job and the adapter passes the exact target agent; results render directly in the UI, not as a tool result or session event (source). The CommandInvocation a handler receives has four fields:

  1. commandId — the pairing id already written to this invocation's command/run event.
  2. agent — the exact agent whose UI received the command.
  3. rawInput — the text right after the registered command name, preserving the separator and trailing whitespace the adapter passed.
  4. signal — the cancellation signal owned by the dispatching UI request.

The returned CommandResult is one of two branches: { kind: 'success', text?, sourceEventSeq? } or { kind: 'error', text }. sourceEventSeq is optional and only used on success: when present it points to an earlier non-command event in the receiving session log, and command/done persists the same reference so clients can merge the command lifecycle with that domain projection without parsing text or relying on adjacent lines.

Discovery, scope, and lifecycle logging for DSH plugin user commands

Scope is layered and the lifecycle is log-only (source).

  1. Scope and shadowing — definitions on a plain context are global; definitions registered through a commands-injected child of an agent context shadow globals for that agent. ctx.commands.register(definition) returns the exact effect disposer; ctx.commands.list(agent) returns name-sorted immutable CommandDescriptors (handler-free) after scoped shadowing; ctx.commands.find(agent, name) returns the shadow or the global definition.
  2. Log-only lifecycle — ctx.commands.execute(agent, line, signal) parses and executes a known command: command/run is appended before the handler is invoked and command/done after settlement (a thrown or aborted handler settles as kind: 'error'). Both are direct log-only appends with no turn wrapping them, drained by persistence at ordinary checkpoints.
  3. Admission and failure semantics — admission misses (syntax errors or unknown names) log nothing, because they never entered a handler; a command/run append failure fails the execution loudly, while a command/done append failure on the handler-failure path is contained so the handler's own error stays the reported failure.
  4. Parse view — parseCommand() returns a ParsedCommand before registry resolution, so a syntactically valid input may still point to a command that is not currently available.
  5. Registry change notification — commands/change is an unfiltered registry notification (a global or scoped change may affect any UI view); observer failures are contained and cannot veto the registry mutation.

Three self-checks when done: is name lowercase with no leading slash; is recordInput set to false when a domain event already carries the payload; and does the handler return a CommandResult directly instead of trying to push the command into a model turn. To find community implementations of command plugins, you can search the DSH Plugin Hub; for how commands differ from tools see the feature-to-mechanism map.

FAQ

How does a DSH plugin register a user command?

A DSH plugin registers a user command through the ctx.commands registry provided by dsh-commands: call ctx.commands.register(definition) with a CommandDefinition, and the registry validates and freezes an effective definition detached from the original object, returning the exact effect disposer for unregistering. Interactive adapters consume the registry, discover plugin-owned commands, and execute them against an exact agent.

Are DSH plugin user commands sent to the model?

A DSH plugin user command is not sent to the model: it executes directly against the receiving agent, so the CommandDefinition handler receives the exact target agent and signal in handler(invocation), and the result renders directly in the UI rather than as a tool result or session event, creating no model message at all. Tools that go through a model turn are a completely different path.

What are command/run and command/done in a DSH plugin?

They are the log-only lifecycle records of a DSH plugin user command: command/run is appended before the handler is invoked and command/done after settlement (a thrown or aborted handler settles as kind: 'error'). Both are direct log-only appends — no turn wraps them, and persistence drains them at ordinary checkpoints. Admission misses (syntax errors or unknown names) log nothing, because they never entered a handler.

What is sourceEventSeq for in a DSH plugin?

sourceEventSeq is an optional field on a DSH plugin success result: when present it points to an earlier non-command event in the receiving session log, and command/done persists the same reference so clients can merge the command lifecycle with that domain projection without parsing text or relying on adjacent lines. It is only used on kind: 'success' results.

How are DSH plugin commands isolated per agent?

DSH plugin commands resolve by scope: definitions registered on a plain context are global, while definitions registered through a commands-injected child of an agent context shadow globals for that agent. ctx.commands.list(agent) returns name-sorted immutable descriptors after scoped shadowing, and find(agent, name) returns the scoped shadow or the global definition.

Related Terms

CommandDefinition
CommandDefinition is the plugin-owned user command registration in DeepSeek Harness, carrying a lowercase name without the leading slash, a description, an optional input.hint, recordInput defaulting to true, and a handler that executes directly against the receiving agent; the registry validates and freezes an effective definition detached from the original object.— https://deepseek-harness.github.io/deepseek-harness/en/reference/subsystems/commands
CommandInvocation
CommandInvocation is the object passed to one registered command handler, carrying the commandId already written to this invocation's command/run event, the exact agent whose UI received the command, the rawInput following the command name, and the signal owned by the dispatching UI request.— https://deepseek-harness.github.io/deepseek-harness/en/reference/subsystems/commands
sourceEventSeq
sourceEventSeq is an optional field on a successful command result that points to an earlier non-command event in the receiving session log; command/done persists the same reference so clients can merge the command lifecycle with a richer domain projection without parsing text.— https://deepseek-harness.github.io/deepseek-harness/en/reference/subsystems/commands
commands/change
commands/change is the unfiltered notification event of the user command registry, emitted when a command is registered or unregistered; because a global or scoped change may affect any UI view it is not filtered, and observer failures are contained and cannot veto the registry mutation.— https://deepseek-harness.github.io/deepseek-harness/en/reference/subsystems/commands

Sources