DeepSeek Harness plugin

dashr

RLM mode for dsh: recursive sub-agents via a unified rlm() function — spawn, await, and chain child agents as first-class Python calls. Adds a persistent IPython kernel with tools.* bindings and context-as-variable.

Jump to install

Source facts

Repository
fgm-builds/dashr
Latest update
Aug 18, 2026
Category
Development & Runtime
GitHub stars
0

Install

Start with a prompt that asks an agent to read the source. Switch to the command if you want to install it yourself.

Copy this prompt into DSH, Codex, or another agent and ask it to read the page and repository first.

Do not install anything yet. Read this DeepSeek Harness plugin and explain what it does, which files, networks, or credentials it can access, and how to install and remove it.

Plugin page: https://deepseekplugins.org/plugins/fgm-builds/dashr~23dashr
GitHub: https://github.com/fgm-builds/dashr/tree/main/dashr
Plugin: dashr#dashr
Author: fgm-builds
Install command: dsh plugin --profile web add dsh-rlm-mode

Do not run the install command until I confirm.

Check the source files

Read the README and other files from this plugin directory before installing.

File explorer3 files
README.mdSource · read only

dsh-rlm-mode

The DASHR plugin for the DeepSeek Harness — the RLM mode: a stateful ctx.rlmRuntime provider (one persistent IPython kernel subprocess per session, the run's principal, held in a map inside the one service instance per mount — the upstream "plugins key their state by Session/Agent" model, which a per-mount realm cannot provide on its own. Each run() is one cell on the calling session's kernel; variables, imports, and definitions assigned in run N survive into run N+1 — state codification (blueprint §1.1 channel ②), deliberately NOT the per-run isolation a one-shot execution backend provides — and two sessions sharing one service instance never see each other's variables.

This is M1 of DASHR: the provider half of the seam. The consumer half — the run_cell transport tool, the Python SDK renderer, and the presentation plugin that binds them to the dsh tool registry — lives in the sibling package dsh-rlm-mode (../dashr-presentation). The provider registers the service key rlmRuntime through its own vendored Service Definition (see src/vendored/rlm-runtime.ts), so it carries zero dsh runtime package dependencies: only @deepseek-ai/cordis (peer), schemastery, and zeromq.

Package positioning

  • npm name: dsh-rlm-mode (local --patch development;

publish scope still open — blueprint §11 #4).

  • A standard Cordis plugin (Context + schemastery Config, every tunable

configurable from cordis.yml, no hardcoded tunables).

  • "Registrations are effects": the kernel lifecycle (lazy spawn on a key's

first run(), teardown on that session's agent/disposed, optional snapshot at either teardown) is effect-owned, so plugin disposal tears every subprocess down.

  • Published surface: lib/ only (files: ["lib"], main/types/exports

pointing at lib/index.js / lib/index.d.ts) — the build emits beside the manifest (outDir: 'lib'; the tsdown default dist/ left the exports map dangling, fixed in M2B). The root also re-exports the vendored Service Definition's public contract (RLMRuntime plus the CodeRun* / CodeBinding* / CodeJsonValue types) so consumers depend on the published shape instead of reaching into sources. The declaration keeps dependency imports external (dts: { resolve: false }): bundled copies would create duplicate type identities in a consumer's program.

Install

npm install dsh-rlm-mode

The provider needs a Python interpreter with ipykernel (and dill for snapshots). For development and tests, create a dedicated kernel venv:

npm run kernel:venv        # uv venv .venv-kernel + ipykernel + dill

Tests pick the kernel interpreter from DASHR_TEST_PYTHON, falling back to ./.venv-kernel/bin/python, then python3 — see test/helpers.ts.

Configuration

Every field of the plugin Config (schemastery defaults shown):

FieldDefaultMeaning
pythonpython3Interpreter with ipykernel installed; spawned with -m ipykernel_launcher.
startupTimeoutMs30000Budget for kernel spawn → ready, in milliseconds.
runTimeoutMs120000Wall budget per run; expiry interrupts the kernel then force-settles.
interruptGraceMs2000Grace between a timeout/abort interrupt and the force-settle.
interruptConfirmMs250Confirm window between the control-channel interrupt and the SIGALRM escalation (must be < interruptGraceMs); see "Interrupts" below.
disposeTimeoutMs5000Budget for graceful kernel teardown (shutdown_request → SIGKILL).
snapshotTimeoutMs30000Budget for internal snapshot/restore cells (dill dump/load).
maxOutputBytes67108864Hard cap for serialized log-array, completion-value, and failure-message payloads.
snapshotDir(unset)Base directory for per-session namespace snapshots (<dir>/<principal>/state.dill + manifest.json); none when absent.
snapshotSizeCapBytes268435456Serialized-size cap for a turn-end snapshot; over-cap snapshots are skipped (one-time model warning).
usernamedashrJupyter username stamped on wire messages.

Persistent-state semantics

  • Cell semantics: each run({ program }) is one cell on the calling

session's kernel namespace (user_ns). Top-level await and return work; the completion value crosses the lossless-JSON boundary (explicit return Nonenull; no return → no value field).

  • Session keying (M3-A): one kernel per distinct request.principal

(the presentation bridge passes the calling agent's session id); runs without a principal share one default key, preserving M1 semantics. The service instance count is unchanged — one per mount — the keying is a Map<principal, kernel> inside the provider.

  • Kernel lifetime: lazy start on a key's first run(); teardown when

that session's agent is disposed (the dsh agent/disposed event, payload { agent: { id } }, listened through the untyped cordis event service to keep this package's zero-dsh-dependency rule) and on plugin disposal (shutdown_request, then SIGKILL after disposeTimeoutMs). A kernel that dies unexpectedly is never reused in-process: it respawns onto its nearest replayable snapshot (or a fresh empty kernel when none exists) and the run that observed the death gets an explicit worker-exit naming what was lost.

  • Turn-end snapshots (M3-B): with snapshotDir configured, every

successful run is followed by a size-capped snapshot cell that dumps the user namespace to <snapshotDir>/<principal>/state.dill + manifest.json (turn, pythonVersion, venvPath = the kernel's own sys.executable, skills, names, sizeBytes). A namespace whose serialized size exceeds snapshotSizeCapBytes is skipped — estimated BEFORE any dill IO by a bounded walk that reads numpy/pandas in-memory footprints, then confirmed against the actual .part dump — and the model is warned once through the run's own logs. Skipped snapshots never replace the previous good one.

  • Restore-on-first-boot (M3-B): a key's first kernel boot restores its

on-disk snapshot before running user code. The kernel validates the manifest itself (python version, interpreter identity, skills); a non-replayable snapshot degrades to an EMPTY namespace and the first run tells the model so. Variable state and the append-only transcript are NOT transactionally consistent (blueprint §8.3): the snapshot is a point-in-time namespace capture that can lag the transcript, and a degraded restore never fabricates variables the transcript once saw.

  • Interrupts (M3-A hardened): aborts/timeouts escalate in two phases —

the zmq control interrupt_request first, then SIGALRM only after interruptConfirmMs if the cell has still not settled. The kernel-side bootstrap installs a busy guard that only raises KeyboardInterrupt while a dashr cell is actually executing, so a signal landing on an idle or booting kernel is swallowed instead of terminating the process (the M1 same-tick dual send killed idle kernels deterministically — 10/10 same-tick, 8/10 at +1-2ms, 40/40 during cold boot; see test/interrupt-race.spec.ts). The hard-abort contract is intact: a busy while True: pass still breaks inside the grace (blueprint §10.4).

  • Concurrency: the bridge serializes cells per kernel (executeCell

awaits the previous cell), so concurrent run() calls on one session queue rather than interleave; see test/parallel.spec.ts. Runs on DIFFERENT principals execute on their own kernels concurrently.

  • What snapshots do NOT carry (M4-B): the Continual Harness — the

presentation-side durable prompt store behind the dashr:harness section and the refine() binding — lives OUTSIDE the kernel namespace and snapshotDir, persisted under its own harnessDir by the presentation package. A snapshot/restore cycle therefore never rolls harness entries back (and a harness edit never invalidates a snapshot): the two persistence channels are keyed by the same agent id but are otherwise independent (blueprint §8.4). Anything a cell stores in ordinary variables follows the snapshot rules above as before.

Testing

npm install
npm run kernel:venv     # once; or export DASHR_TEST_PYTHON=/path/to/python
npm run typecheck       # tsc --noEmit
npm test                # vitest --run (fileParallelism: false)

Teardown discipline: every test context is disposed through onTestFinished, and CI must assert no orphan kernels remain:

pgrep -cf -- '-[m] ipykernel_launcher' || echo no-orphans

(The -[m] trick prevents pgrep from matching itself; 208 orphaned kernels once exhausted machine memory while every unit test stayed green — blueprint §10.8/§10.9.)

---

Presentation half (run_cell transport, SDK, bindings, harness)

dsh-rlm-mode

The DASHR agent-plane presentation row (blueprint §7.4): the plugin an agent preset carries to present the RLM runtime's tools to the model as cells on a persistent IPython kernel.

Mounted in a preset's standing scope, it contributes:

  • run_cell — the only tool the model may call directly. One call = one

cell on the persistent kernel (ctx.rlmRuntime, provided by the sibling package dsh-rlm-mode). Variables, imports, and definitions survive across calls. Nested tool calls ride the host registry's native scheduling pipeline (await tools.name({...}) inside the cell; member bindings are positional, keyword arguments are rejected). Two BARE callable globals are also installed per cell: await rlm(prompt, label=None) and await rlm_await(run_id) (see "rlm() subagent binding").

  • tools:dashr-sdk — a generated Python SDK prompt section: one named

TypedDict per tool argument/output object, one awaitable method per visible tool on a Tools protocol, and the cell contract (persistent namespace, completion-value rules, ToolCallError, sub-call concurrency).

  • The model-direct collapse — an assembly filter leaves run_cell the

only contributed tool schema, and a monotonic guard denies a model-direct call naming anything else with the route back into a cell. Both are scoped to the mounting composition, so a PTC (native Code Mode) preset in the same process keeps its own presentation.

Install

dsh plugin add dsh-rlm-mode

That installs this package — and, through its peer chain, the dsh-rlm-mode kernel provider — into the dsh profile. --patch variants (dsh plugin --patch ... / a profile overlay) work the same way; the package is a plain npm install from the profile's perspective.

Two more setup facts:

1. Kernel interpreter. The provider spawns a Python interpreter with ipykernel installed (plus dill if you want dispose-time state snapshots). The shipped preset resolves it from DASHR_KERNEL_PYTHON, falling back to python3. A dedicated venv keeps it clean:

``sh python3 -m venv ~/.dashr-kernel && ~/.dashr-kernel/bin/pip install ipykernel dill # then: export DASHR_KERNEL_PYTHON=~/.dashr-kernel/bin/python ``

2. Preset root. A preset is a directory holding agent.cordis.yml; the roster (@deepseek-ai/dsh-agent-presets) only scans its configured roots. This package ships the preset at preset/dashr/, so expose it to the roster by adding that directory as a root — the same mechanism the CLI uses for its own shipped set (apps/cli/src/profile-boot.ts pins config/agent-presets/ with trust: system via a boot overlay). With a --patch overlay (or the profile's cordis.patch.yml):

``yaml # dashr-preset-root.yml — passed as --patch dashr-preset-root.yml - id: agent-presets config: roots: - path: <profile-dir>/node_modules/dsh-rlm-mode/preset trust: system ``

roots entries are scanned in order (earlier wins a duplicate id), each path may expand a leading ~, and trust marks shipped (system) vs locally authored (user) presets — display-only, not a capability boundary. The roster always appends its own user root (<dshHome>/.agent-presets) unless includeUserRoot: false.

Once the root is configured, the preset appears in the roster and can be picked for a session (dashr), copied for local authoring, or set as the agent-presets default.

The dashr preset

preset/dashr/agent.cordis.yml (display metadata in preset.yml) is an AGENT-PLANE composition in the shape of the upstream code preset. Its rows:

RowPackageNotes
persona@deepseek-ai/dsh-personaSame shape as code; describes the persistent-kernel mode.
agent-instructions@deepseek-ai/dsh-agent-instructionsSame as code.
dashr-kernel (group, isolate: { rlmRuntime: true })dsh-rlm-mode + dsh-rlm-modeThe provider publishes ctx.rlmRuntime behind an entry-local realm; the presentation row sits INSIDE the group because realm-private services resolve only for rows sharing the realm.
filesystem (group, isolate: { fs: true })@deepseek-ai/dsh-fs-local + @deepseek-ai/dsh-tool-fsThe minimal preset's bare-local pattern (the code preset instead uses the host's sandboxed fs). read/write/edit register on a bare host; read_image waits for an attachments service the host owns.
tool-todo@deepseek-ai/dsh-tool-todoRegisters into the registry's preset layer; also the binding-bridge material (tools.todo_write(...) inside a cell).

Deliberately absent, with reasons (the upstream code preset carries them):

  • dsh-tool-bash / dsh-tool-pwsh — their executors (bash-sandbox /

pwsh-sandbox) are host-plane services a bare host does not supply, and shell work belongs inside the kernel anyway.

  • dsh-tool-fs-search — its ripgrep/subprocess/spill stack is host-plane

weight with a native dependency; kernel-side Python covers search.

  • The jobs/skills/goals/plan/compaction/delegation sections — each either

owns host-plane singletons or adds host services; a DASHR deployment composes them on the host when wanted.

The provider row's config carries only the tunables worth overriding from a preset: python (from DASHR_KERNEL_PYTHON, else python3), snapshotDir (unset → no snapshots). The kernel's working directory is NOT a tunable: it is per-session state, resolved at kernel boot from the run principal through the host's sessions service (session.header.cwd — the same source the {{cwd}} prompt variable reads), so each session's kernel starts in that session's workspace. A principal with no resolvable session (agentless runs) falls back to inheriting the host process cwd. See the sibling package's README for the full table.

Realm semantics (read this before relying on isolation)

An entry-local realm (isolate: { rlmRuntime: true }) is one instance per mounted composition, not per session. The roster mounts a preset ONCE per process under a standing scope and every session joins it, so under the roster all dashr sessions share one provider instance. That is the upstream roster's documented model ("its plugins key their state by Session/Agent, so sessions stay apart inside one shared instance") — and since M3-A the provider honors exactly that: it keys one kernel per Session/Agent inside the shared instance (the run's principal, threaded from the calling agent's id by this package's bridge), spawns each lazily on that session's first run_cell, and tears it down on agent/disposed. State set by session A is therefore NOT visible to session B under either mount granularity; mounting per agent (the exported mountPreset primitive) additionally gives each session its own realm instance. test/preset.spec.ts proves both directions (shared instance + keyed kernels under the roster; separate instances under per-agent mounting).

What the realm does guarantee, and what the tests assert: the provider is invisible to the host plane (ctx.get/root realm never resolve it), a mount publishing an un-realm'd service is rejected by dsh-agent-presets, and a PTC Code-Mode session in the same process still resolves the host's codeRuntime.

Coexistence with a PTC Code-Mode session

run_cell is our own transport name (the registry reserves run_code), so a Code-Mode preset (@deepseek-ai/dsh-agent-tool-presentation with mode: code over the host-plane worker-thread codeRuntime) composes beside the dashr preset in one process: the PTC agent's assembly shows run_code plus the TS tools:sdk section, the dashr agent's shows run_cell plus the Python tools:dashr-sdk, and neither execution path touches the other's runtime. One environmental caveat: the worker-thread provider strips TypeScript in-process, so a Node build without TS support (process.features.typescript === false, e.g. this dev box's v22 binary) runs Python cells fine but answers a run_code with the provider's documented "Node.js is not compiled with TypeScript support" error — the same spec passes the real-run branch under a TS-capable Node 24.

Composition

import Presentation from 'dsh-rlm-mode'

// Inside a preset's standing scope context:
scope.ctx.plugin(Presentation, { maxParallelSubCalls: 10 })

The row waits for ctx.rlmRuntime at mount (ctx.inject) and re-reads it at use time: a preset against a runtime-less deployment fails at mount, named in the preset's activation audit, instead of at the first prompt.

rlm() subagent binding (M3-B; model selection added in M4-A)

Each cell installs two bare callable globals on top of the tools namespace:

  • handle = await rlm(prompt, label=None, model=None) — non-blocking ADMISSION

of a child agent through the host-plane ctx.subagents service, in-process provider spawn first (blueprint §9). The await resolves when the child is PUBLISHED, not when it finishes — the same admission semantics as the RLM runtime's own rlm(). Returns {run_id, label, provider: 'spawn', local, model}; the child keeps running after the cell returns and is cancelled by the enclosing run_cell's outer signal.

  • Child-model selection (M4-A) is a three-level priority:

rlm(model="...") > the composition's subagentModel config > the parent agent's own model. The first two tiers reach the harness as agentOptions: { model } on the start request (the handle's model field reports what was resolved, null for inheritance); when BOTH are unset the request carries no agentOptions at all and the harness's own parent-inheritance applies — this plugin never names the parent model itself. model=None is "unspecified" (falls through to the config tier), and any non-string value is rejected as a result error, never a host crash.

  • result = await rlm_await(run_id) — blocks the cell until that run settles

and returns {output: str, stop_reason: str, structured: Any|None}

(output is the child's final text, with non-text blocks folded to compact markers). The wait is interruptible by the cell's own timeout/abort.

Both bindings return structured JSON; errors are a FIELD on the result, never a host crash — no ctx.subagents service, no spawn provider, an unsupported capability, a depth cap, an unknown run_id, or an infrastructure rejection all map to an error string (or rlm_await's stop_reason: 'error'). Live handles are held host-side per composition, settled/removed by rlm_await, and every unsettled run owned by a session is disposed on that session's agent/disposed (and all of them on composition teardown).

Realm boundary: ctx.subagents is a HOST-PLANE root-realm singleton (the dashr preset deliberately does not carry the subagent rows), while this row sits inside the preset's isolate: { rlmRuntime: true } realm. Cordis resolves oute