← Back to home@kuun993

dsh-loop-engine

No description

Stars
5
Language
TypeScript
Created
Aug 25, 2026
Updated
Oct 5, 2026
GitHub repo

Introduction

dsh-loop-engine

npm version

Pick dsh web's agent loop engine per session — the built-in in-process loop (the default), or one of the hosted engines claude-code / codex / pi / kimi. Sessions are independent: one chat can run Codex while another runs Kimi. Any open, idle session can move engines — between two hosted engines the handover is in place, and a move involving in-process reloads the page (the dsh web process never restarts). Nothing in the main repository changes.

Install

dsh plugin --profile web add dsh-loop-engine

Boot dsh web once (the router retries for a bounded window while the base bundle still holds the agent-factory slot), then open Settings → Loop engine.

Install notes

  • The install rewrites one engine-agnostic managed block in the profile's cordis.patch.yml; every other byte you wrote there is preserved. Upgrading from an older release needs no hand edit.
  • Installing into any profile but web — the Desktop app's desktop, a headless deployment, a renamed profile — needs profile: <name> in the composition entry for that profile:
    - id: loop-engine
      config:
        profile: desktop
    
    profile defaults to web, so without it the plugin writes its managed block into <home>/profiles/web/cordis.patch.yml while the running profile never reads it: the plugin mounts, but the base agent-loop row is never disabled, so its router cannot take the factory slot and no hosted engine is reachable. (Found by installing into the Desktop app.)
  • Uninstall is dsh plugin --profile web remove dsh-loop-engine plus deleting that managed block — the block outlives the plugin, and while it is present the profile has no agent factory, so no session can be created.
  • pnpm 10+ may block dependency build scripts (ERR_PNPM_IGNORED_BUILDS, naming esbuild, @google/genai, protobufjs). Run pnpm approve-builds (or add an allowBuilds entry) and retry; only the installing project can grant this.
  • Booting the harness from its source checkout additionally needs the profile's harness peers bridged with file: shims — otherwise session resume fails with agent-presets: refusing to compose an unscoped context. Steps in docs/source-checkout.md.

Requirements

EngineNeeds, on the host
Claude Codethe Claude Code CLI installed and logged in
Codexcodex login, or a CODEX_API_KEY environment entry
Piwhatever pi expects (its own ~/.pi/agent/auth.json, or the provider's API-key variable)
Kimi Codethe kimi CLI installed and logged in, on PATH (or pinned via kimiBin)

Usage

  • New session — pick the engine on the preset chip beside the workspace picker, or keep the deployment's default (standard) for the in-process loop. A child agent inherits its parent's engine.
  • Settings → Loop engine sets the default for new sessions. It lands immediately; running sessions are unaffected. In-process restores whatever default the plugin replaced.
  • Move a session you are in with the composer's engine selector (enable Show the engine selector in the chat page).
    • Hosted → hosted: in place, applies on the spot.
    • Anything involving in-process: the composer asks first, then the page reloads and reopens the same session (scroll position and unsent draft are lost; the record is not).
    • A mid-turn session is refused, and a subagent's session cannot be moved.
  • The header chip beside the preset label names the engine the session on screen actually runs; Show the engine badge in the conversation header turns it off (and the header keeps no trace of the plugin).
  • Models — all hosted engines share one external group whose single entry, default, means "the engine decides". Picking a real dsh model hands it to the engine together with its endpoint and credential; whether the engine can serve it is the engine's business, and a refusal is reported rather than swallowed. in-process sessions use dsh's models normally.
  • childIdleMs (composition entry, milliseconds, default 0 = off) — Kimi and Codex keep one child process per session across steps; this closes it after that much idle time and respawns it on the next step. Only the child is closed, so nothing reloads. Pi and Claude Code spawn per step and are unaffected.
  • engineContextWindow (composition entry, tokens, default 262144) — the context window dsh assumes for the shared hosted route. It is what dsh's auto-compaction measures a hosted session against (it fires at 80% of this by default), because a hosted engine reports no capacity of its own. Set it to the smallest window among the engines you run.
  • summarizerProvider / summarizerModel (composition entry, set together or not at all) — the real dsh model a hosted session's compaction summarizes through, e.g. deepseek-official / deepseek-flash. Compaction is dsh's own (its preset keeps the compaction group), but the summarizing call is an ordinary llm request, and a hosted session's routed model is the plugin's placeholder route — so without this pair a hosted session can never be folded down: every attempt logs a warning and the turn continues. The pair only serves that call; the engine still answers every user turn itself. Composing just one of the two (or an empty string) is refused at boot, not silently ignored.
  • dsh's own commands (/export, /feedback, /permission) keep working under a hosted engine. Its preset is a copy of standard with the dsh-native rows an external engine replaces stripped out (dsh's /plan, the goal tool and /goal, the skill rows). dsh's compaction is kept: it is what keeps the session log — and so the transcript replayed to the engine on every step — bounded, which an engine's own per-turn compaction cannot do (a fresh engine session is opened per step, so the engine's shrink is discarded while the log keeps growing). Configure summarizerProvider / summarizerModel (above) or its summarizing call has no model to run on.

Version compatibility

One release serves the 0.1.5 line (>=0.1.5-rc.1 <0.1.6-0), the 0.1.7 line (>=0.1.7-rc.1 <0.1.8-0) and the 0.2.0 line (>=0.2.0-rc.1 <0.2.1-0): the plugin detects the running generation at load and takes the matching code path. Those union ranges are what it declares in peerDependencies, and a harness outside them fails loudly at boot or session resume.

Read the version as <harness line>-rcN, where rcN is this plugin's own release counter for that line — not the harness's rc number. Within a covered line a new harness rc needs no plugin release unless an API surface moved; docs/compatibility.md §1 lists those surfaces and the diff command that decides it.

PluginHarness
0.2.0-rc10.2.0-rc.1, 0.2.0-rc.2
0.1.7-rc1 … 0.1.7-rc50.1.7-rc.1, 0.1.7-rc.2
0.1.5-rc3 … 0.1.5-rc50.1.5-rc.2
0.1.5-rc1 / 0.1.5-rc20.1.5-rc.1
1.0.0-rc8 … 1.0.0-rc150.1.2-rc.1
1.0.0-rc7 and earlier0.1.1-rc.2

0.2.0-rc1 is the first release that serves three lines at once, and the first one published after the harness started enforcing a peer-range gate at boot: the plugin is still mounted alongside an incompatible one, but a harness outside the ranges above is now skipped by dsh itself rather than failing later.

Known limitations

  • It replaces the harness's agent factory. One facade serves every session, so the plugin promises to stay out of the way: any session it cannot answer for — including sessions with nothing to do with hosted engines — degrades to the in-process loop instead of failing to open. See docs/per-session-engine.md §6.
  • The engine record is a sidecar, $DSH_HOME/.loop-engine/engines.json, not part of the session log: changing machine or DSH_HOME loses it and the session falls back to its preset. See §5.5.
  • Moving a session involving in-process reloads the page — the harness loop neither hands over a live session nor adopts one it did not create. See §5.2/§5.4.
  • Sessions that already ran a turn can still move, but only while idle, and the move rebuilds that session's agent. See §5.
  • Old sessions with the legacy preset id loop-engine read as "legacy hosted engine" until rebuilt once. See §7.
  • Same-engine sessions share that CLI's own auth directory, with no lock added by the plugin. See §6.
  • Switching back to in-process leaves the shared external group in the model menu (the catalog is not scoped per session).

Where the details live

License

MIT