DSH plugin terminal subsystem: persistent PTY and ownership

Plugin DevelopmentPublished 2026-10-17Author: DeepSeek Plugin Market
DeepSeek HarnessDSH pluginterminalPTYTerminalBackendbounded reads
How a DSH plugin plugs into persistent PTY sessions: service-minted ids, exact-owner access, waitReason versus status, and one active send.

The DSH plugin terminal subsystem is one service plus replaceable PTY backends: the service mints ids and owns authorization and cleanup, while a backend only starts its own type. Two rules catch people out: authorization compares the exact owning Agent, not a name or a guessed id; and a send returning does not mean the shell exited — waitReason may be silence or a timeout, so top-level state comes from sessionStatus. Add "one active send per session" and you have the whole hard contract.

DSH plugin terminal service: service-minted ids, exact-owner access

Bottom line: ids and authorization live in the service; names are display-only, and a wrong owner is rejected outright.

  1. Id source: TerminalSessionId is minted by the service; callers never generate one.
  2. Name role: an optional name is owner-local display metadata and does not authorize anything.
  3. Authorization subject: the exact owning Agent.
  4. Activity window: hasOwnerActivity(owner) is true from spawn to close with no publication gap.
  5. Visibility: list(owner) returns only that owner's snapshots in publication order.

DSH plugin backend contract: publish only successful sessions

Bottom line: publish after initialization succeeds; cleanup failures use a dedicated error instead of replacing the cancellation reason.

  1. Stable type: TerminalBackend.type is selected by TerminalSpawnRequest.type.
  2. spawn semantics: return an unpublished session or reject after cleaning partial resources.
  3. Cleanup failure: reject with TerminalBackendCleanupError while preserving the caller's cancellation reason.
  4. Backend duties: own terminal state and bring captured resources to quiescence.
  5. Service duties: publish, then own id authorization and cleanup; close(reason) is idempotent and repeated calls share one teardown.
  6. Initial output: motd is the bounded initial output from terminal_open, not the full scrollback.

DSH plugin sends: waitReason and sessionStatus are orthogonal

Bottom line: a send returning explains why control came back; session status separately says whether the shell is still alive.

waitReasonMeaning
stdin_readThe process is waiting for input
inferred_idleInferred idle
timeoutThe wait timed out
session_exitThe top-level shell exited
  1. Orthogonal: a send may return on silence or timeout while the top-level shell keeps running.
  2. Scope of session_exit: the shell exited, not some arbitrary foreground child.
  3. Session status: { kind: 'running' } or { kind: 'exited', exitCode, signal }.
  4. Send result: viewport is the bounded rendered delta left at settlement, and truncated marks dropped output.
  5. Cancel: cancel() requests SIGINT and returns false once the operation has settled.
  6. Signals: signal(owner, id, allowedSignal) targets only the verified foreground process group.

DSH plugin bounded reads and cursors: foreground versus background

Bottom line: foreground callers get a final result; background jobs get a read-after-advance cursor; scrollback pages separately.

  1. One active send: per session, never more.
  2. Background shape: the operation offers an advancing output cursor for generic background jobs.
  3. Foreground shape: done settles with viewport, waitReason, sessionStatus and truncated.
  4. Scrollback pages: read(owner, id, { offset, count }) returns one bounded retained page, independent of the send cursor.
  5. Assume bounds: both return paths are bounded; never expect a full dump.
  6. Verify: read increments for a long-running command via the cursor, then page scrollback separately and compare.

DSH plugin ownership, reload and durability: PTY bytes stay local

Bottom line: cleanup attaches to the exact owner scope, sessions survive backend and tool reloads, and raw bytes never reach the log.

  1. Cleanup scope: the service attaches wait-for-completion cleanup to the exact owner and rejects other owners.
  2. Reload survival: published sessions stay alive across backend or tool-plugin reloads.
  3. Process-local boundary: PTY state and raw bytes never persist with the log.
  4. Persist only bounded content: model input and bounded returned output ride the existing tool/call, tool/result and task-result paths.
  5. Close to quiescence: kill removes a session only after backend cleanup settles; repeated calls return false.
  6. Diagnostics: close(reason) carries a cleanup reason, so record what triggered it.

DSH plugin terminal troubleshooting

Bottom line: separate authorization refusal, send contention, wait misreading and truncated output.

  1. Operation refused — use the exact owner Agent; names and guessed ids fail.
  2. Second send hangs — only one active send per session; await done or cancel.
  3. Assuming the process exited — read waitReason and sessionStatus, not just the return.
  4. Output incomplete — truncated is true or the read is bounded; page scrollback instead.
  5. Session lost after reload — check whether backend cleanup closed it; normally sessions survive.
  6. Repair status, stated honestly: identity and authorization rules, backend contract, wait reasons and durability boundaries follow the official Persistent PTY sessions page; confirm signatures against its Cordis API and your version.

Sources: Persistent PTY sessions, terminal source, Subprocess.

FAQ

Can a DSH plugin mint its own terminal session id or guess one by name?

No: a DSH plugin TerminalSessionId is minted by the service, and the optional name is only local display metadata. Authorization compares the exact owning Agent, so names and guessed ids never pass.

If a DSH plugin terminal send returns, does that mean the process exited?

Not necessarily: a DSH plugin waitReason explains why the send returned, including silence, timeout or input readiness, and only session_exit means the top-level shell exited. Session state is reported separately by sessionStatus.

Can two sends run concurrently in one DSH plugin terminal session?

No: a DSH plugin session accepts exactly one active send at a time. A second send waits for the previous done to settle or must cancel first, and the output cursor advances inside that operation.

If a DSH plugin backend fails halfway through startup, does cleanup replace the cancellation?

No: a DSH plugin backend rejects with TerminalBackendCleanupError after cleaning partial resources, preserving the cleanup failure without replacing the cancellation reason. The service publishes a session only after initialization succeeds.

Do raw DSH plugin terminal bytes end up in the session log?

No: DSH plugin PTY state and raw bytes stay process-local. Model input and bounded returned output persist through the existing tool/call, tool/result and task-result paths rather than duplicating PTY session events.

Related Terms

TerminalSessionId
TerminalSessionId is the branded identifier minted by the DSH plugin terminal service for authorization, reads and close. An optional name is local display metadata and never participates in authorization.— https://deepseek-harness.github.io/deepseek-harness/reference/subsystems/terminal
TerminalWaitReason
TerminalWaitReason explains why one interactive send returned control, taking the values stdin_read, inferred_idle, timeout or session_exit. It is orthogonal to the session's top-level status and does not mean an arbitrary child exited.— https://deepseek-harness.github.io/deepseek-harness/reference/subsystems/terminal
TerminalBackend
TerminalBackend is a replaceable provider for one PTY type, selected by a stable type through the spawn request. It starts sessions and detects readiness, and rejects with a dedicated error when partial resources cannot be cleaned.— https://deepseek-harness.github.io/deepseek-harness/reference/subsystems/terminal
TerminalSendOperation
TerminalSendOperation is one active send, offering a done result, a readOutput cursor and cancel. Each PTY session allows exactly one such operation at a time.— https://deepseek-harness.github.io/deepseek-harness/reference/subsystems/terminal

Sources