Skip to content

Latest commit

 

History

13 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

dsh-rlm

Recursive Language Model (RLM) plugin for deepseek-harness (dsh). A port of the pi-agent RLM plugin (rlm.pi/pi-plugin/rlm) onto dsh-native infrastructure — Cordis lifecycle, worker-thread code execution, stream-only LLM access. Zero duplicated logic: the REPL is dsh-code-runtime, the model port is dsh-llm, persistence is the dsh session log.

Status: Phases 0–4 + headless e2e gate complete. 236/236 offline vitest specs green, strict tsc clean, real-CLI e2e green via e2e/run.sh. See PLAN.md for the port plan, verification log, and phase gates.

What it is

An RLM run keeps the long context OUT of the model's history (paper invariant #1): the model interacts with it by writing TypeScript in ```repl fenced blocks. Programs run in a worker thread; host-side bindings give them:

  • rlm.llm_query / llm_batch / rlm_query / rlm_batch — async-by-default sub-LLM fan-out (spawn → task_idawait_task), deduplicated by the session TaskLedger (exact-hash coalesce, ancestor-echo reject, Jaccard near-dup coalesce).
  • rlm.finish({ summary }) — the ONLY final-answer channel.
  • env.set/get — durable JSON variables across blocks (host-side, decision D2).
  • ctx.search / grep_context / get / outline / list / add_context — free local work over the context bundle.
  • memory.* — durable cross-run note store (BM25 recall, leaf-model evolution).

The engine loop enforces the paper invariants: metadata-only history, token-budget cascade with deterministic continuation handoffs (distillTrajectory), compaction/elision, LimitGuard caps, and depth-capped recursion that degrades rlm_queryllm_query near the cap.

Install (profile mount)

The loader imports absolute-path name specifiers directly — no publish step:

# $DSH_HOME/profiles/<name>/cordis.patch.yml
- insert:
    # Required ONLY if your bundle stack has no code-runtime provider (dsh-base
    # ships none; the headless bundle already inserts this exact entry).
    # `pending (waiting for service: codeRuntime)` at boot means you need it.
    - id: code-runtime
      name: '@deepseek-ai/dsh-code-runtime-worker-thread'

    - id: rlm
      name: /absolute/path/to/dsh-rlm/src/index.ts
      config:
        provider: deepseek-official   # LLM route (see Config below)
        model: deepseek-chat

Requires Node ≥ 23.6 (the CLI loads the plugin's TypeScript source via type stripping — which is also why the codebase forbids parameter properties). Bare @deepseek-ai/* imports resolve through the profiles flat fallback ($DSH_HOME/profiles/node_modules); for local development symlink them into this repo's node_modules.

As an agent preset (per-session opt-in)

Instead of the process-wide profile patch, author a preset: create $DSH_HOME/.agent-presets/rlm/agent.cordis.yml as a copy of the shipped standard composition with the rlm row appended (plain row — the plugin registers into the host tools registry and provides no service, so no isolate realm is needed), plus a preset.yml (name/description/order). The code-runtime host entry above is still required. Keep the row OUT of the profile patch in this mode — registering the tool twice (host + preset) is untested and may collide.

Config

All fields optional; validated by Schemastery at load.

Field Default Meaning
provider "deepseek-official" Provider route for root and sub-calls
model "deepseek-chat" Root model id
leafModel model Leaf model for llm_query/llm_batch
maxDepth 4 Recursion cap; rlm_query degrades to llm_query at the cap
maxIterations 30 Root loop iteration cap
execTimeoutS 120 Per-block REPL wall-clock budget, seconds
maxConcurrentSubcalls 16 Process-wide leaf-call concurrency
maxConcurrentChildren 6 Per-depth child-run concurrency
enableTokenBudget true Token-budget cascade + continuation handoffs
contextLoader true Mount the context pipeline (ctx.* bindings)
autoSeedCwd true Seed REPL context from the tool call's cwd
enableMemory true Durable notes under memoryDir
memoryDir run cwd Memory directory
injectNoteTokens 2000 Token budget for the injected memory note
evolveEvery 8 Evolve the memory note every N completed runs

Injected services: llm, tools, codeRuntime (Cordis enforces declaration at runtime).

Client half (web profile)

src/client/index.ts is a Cordis client half (inject: ["slots"], named apply) registering a keyed tool.call.toolview component that renders the run card. The tool's renderText emits a machine-parsable summary line — RLM · 3 iter · 12,345→8,001 tok · 4.2s · $0.0012 — from src/ui/summary.ts (the single spec: one format function, one parse function, a round-trip test locking the pair). Generic clients and the card consume the same render.

E2E gate

./e2e/run.sh

Boots the real dsh CLI offline against a scripted LlmAdapter (e2e/stub-llm.mjs), mounts this repo's source into a fresh profile, runs one agent turn, and asserts the zstd session JSONL carries tool/result with isError:false, the one-iteration summary line, and the engine's answer. SIGINT mid-run exits cleanly with no leaked workers.

Layout

src/
  engine/    loop, budget cascade, ledger, limits, compaction, answer/history
  fanout/    task registry, spawn/await handlers, leaf completions, admission gates
  runtime/   ReplSession + binding bridge (rlm/env/ctx/memory namespaces), env store
  context/   context merge/narrowing + local query tools (search/grep/get/outline)
  llm/       completeStream — the ONE stream→completion fold (dsh LlmRuntime port)
  memory/    durable-note store, BM25, hashing, replay
  tool/      defineTool seam: schema, output.render, execute → engine
  ui/        summary-line spec + pure block→card-model projection
  client/    browser half: keyed toolview component + slots registration
  prompts/   system contract + per-turn user prompts
  text/      fence grammar, previews, truncation (wire contract — do not drift)
tests/       29 spec files, 236 tests — fully offline
e2e/         profile template, scripted adapter, run.sh gate

Develop

pnpm install
pnpm typecheck   # strict tsc (strict, noUncheckedIndexedAccess, verbatimModuleSyntax)
pnpm test        # vitest — offline, deterministic

Rules honored: strict TS (no any, no !, no parameter properties), Result<T,E> at boundaries, frozen configs, pre-sized collections, .join('') assembly, files under 1k lines (largest: 398), DRY — one task registry, one stream fold, one claim path, one summary spec.

Roadmap

  • rlm_test worker-level scenario replay (parked)
  • Live mid-run fan-out tree in the client — blocked on a mid-call progress channel for plain tools (upstream harness work, not a local gap)
  • Locale seat for the toolview; css-module styling (currently inline frozen styles)

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages