Repository navigation
DeepSeek Harness (dsh) as a fourth host - #80
Merged
Merged
Conversation
`aloop install dsh` (alias `deepseek`) renders a `dsh --profile aloop` profile whose native Cordis plugin (plugin/hosts/dsh/aloop.mjs) runs the Claude Code gates on dsh's own events: tools/pre-execute denies, tools/post-execute blocks an invalid artifact, and a child's agent/turn-stopping steers it up to 3 times before BLOCKED (or STUCK, released unverified) is injected into the supervisor. Roles are agent presets on two subagent tools, one per model route (deepseek-v4-pro, deepseek-flash); `aloop bind` moves a writer's working directory into its worktree and host-adapter confines it there. Usage is written Claude-shaped per role, so budgets, the ledger and run-summary are unchanged. Tests: dsh-plugin.test.mjs (29 checks, emulated Cordis context) and dsh-runtime.test.mjs (23 checks on the real pinned dsh 0.2.1-rc.2 against a scripted model); CI's hosts job installs the pinned dsh. host-adapter, model-routes, context-boundary and e2e-live-hosts gain dsh cases. `make e2e-live HOST=dsh` drives it live and exits 2 with the fix when no DEEPSEEK_API_KEY is set. Also fixes OpenCode's gate_blocking (outer, matching its adapter). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AiLhAWTRtwbgMCDQNJUbea
An adversarial review of the dsh host before merge found these, all reproduced on the real dsh 0.2.1-rc.2 and now tested: - Worktree escapes: bash's own `workdir` argument was dropped (dsh's bash has one), relative paths were normalised before following a symlink, a `cd` out of the worktree was not checked, and an `aloop bind` in a comment or chained to a command skipped every check (Codex too). - tool_input spread the model's arguments, so an extra `agent_type` key switched off gate-supervisor-scope; it is now built from each tool's declared fields only. - Gates ran without CLAUDE_PLUGIN_ROOT, so gate-validate-artifact crashed and blocked every valid planning artifact; the plugin passes it and the three gates lacking a default now have one. - Gates got the full environment (the DeepSeek key reached worker-run tests); they now get dsh's own shell scrub, keeping aloop's settings and git's GIT_CONFIG_* set whole. - A worker interrupted while its gate ran was steered back to work; a role's send_message reached the supervisor before its stop gate. - One project's aloop-init re-rendered another's global dsh profile (and e2e-live the user's own); the profile now records the project it serves. - A provider-qualified model id split at the last '/'. - gate-supervisor-scope let the supervisor edit source inside a task worktree (all hosts). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AiLhAWTRtwbgMCDQNJUbea
…t-f3orp6 # Conflicts: # CHANGELOG.md # docs/ROADMAP.md
…t-f3orp6 # Conflicts: # docs/ROADMAP.md
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AiLhAWTRtwbgMCDQNJUbea
Master's M9.9 wired the mission roles on Claude Code, Codex and OpenCode; on dsh their presets rendered but neither gate ran. Now: - gate-mission-scope runs on every dsh write, edit and command (supervisor and roles); - gate-mission-artifact records a mission role's start and checks its artifacts at its stop (a block steers the role, as for every other role's gate); - `dsh <profile>` from a tool call is a host session gate-mission-scope refuses where a mission is set (its prompt would be captured as the human's words, M9.17); `dsh plugin` is not; - the mission skill installs for dsh as aloop-mission and uninstall removes it; - the dispatch hint and the roles' dsh note name every role of each route group (the note is derived from _routes.GROUPS). Tests (each new check fails on the code before it): dsh-plugin.test.mjs 36, dsh-install 7, mission-roles 33, mission-scope-bash 225 commands. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AiLhAWTRtwbgMCDQNJUbea
- host-adapter: the bind-only rule takes a strict word class, so a chained command after `aloop bind` is no longer allowed; a vanished working directory denies shell and write tools. - dsh plugin: read the directory dsh's tools use (ensure, not get); drop gate switches dsh loaded from the project .env; an interrupted root Stop injects its reason; warn when the profile serves another project. - gate-supervisor-scope: resolve the path (.., symlinks, relative to cwd) before classifying it; the task.env fallback needs an agent id. - install: the dsh profile records the project it serves and keeps it on a re-render from another project; status says so. - e2e-live: the seed init does not re-render host profiles. Each fix has a test that fails on the old code. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AiLhAWTRtwbgMCDQNJUbea
…M9.24/M9.25) into the dsh host README takes the new front page and adds DeepSeek Harness to its host list, install notes, harness table, CI notes and limitations. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AiLhAWTRtwbgMCDQNJUbea
adeelahmad
marked this pull request as ready for review
October 20, 2026 08:20
adeelahmad
pushed a commit
that referenced
this pull request
Oct 20, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Aw6CiQcdpzdzZZXKf8WbUM
adeelahmad
pushed a commit
that referenced
this pull request
Oct 20, 2026
…42 and the reader Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com&g 8000 t; Claude-Session: https://claude.ai/code/session_0197nSutKR1QAAqgJhFZ8fPj
adeelahmad
pushed a commit
that referenced
this pull request
Oct 20, 2026
…anch CHANGELOG keeps both sides. ROADMAP M9.31 now names the dsh plugin (aloop.mjs), whose PreToolUse list mirrors hooks.json by hand and so lacks `mission doors hook`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012rsDo9GYzNx8tXucywZHnA
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Requested by Adeel · project thread
Before: aloop ran on Claude Code, Codex and OpenCode only. DeepSeek's own harness (
dsh) had no profile, no gates and no role dispatch.After:
aloop install dsh(aliasdeepseek) renders adsh --profile aloopprofile, and the loop runs on dsh with the same gates as on Claude Code. A plugin running inside dsh does three things:Roles are dispatched as agent presets on two subagent tools, one per model route:
deepseek-v4-profor planning anddeepseek-flashfor work by default.MODEL_PLANNING_DSH/MODEL_WORKER_DSHoverride them. The mission layer's six roles run on dsh too, withgate-mission-scopeon every write and command andgate-mission-artifactat each role's start and stop. The profile records the project it serves (or--global), so another project'saloop-initnever re-renders it, and a session in another project is told to runaloop install dsh --project <it>.How:
plugin/hosts/dsh/aloop.mjsmaps dsh's plugin events onto the existing Claude-shaped gate scripts:tools/pre-executetools/post-executeagent/createdagent/pre-stepagent/turn-stoppingagent/turn-stoppingsession/eventusageNo gate is reimplemented in JS. A gate that cannot run denies the call (fail closed).
tool_inputis built only from each tool's declared arguments. Gates get the environment dsh gives the worker's shell, which does not include the API key, and never a gate switch dsh loaded from the project's.env.aloop bindmoves a writer's working directory into its worktree.host-adapterconfines dsh writers there: bash's ownworkdir, acdout of the worktree,working_directory, symlinks followed the way dsh follows them, and a working directory that vanished (dsh's tools then fall back to the main tree, so the plugin judges the directoryensure()returns). Reads stay unconfined.Supporting changes: routes, capabilities, a
Dshadapter in_host.py(enforce_gate: native), config keys,aloop-init, skills and README docs, andmake e2e-live HOST=dsh. The last exits 2 with the fix when noDEEPSEEK_API_KEYis set, and it never touches the user's own dsh profile or Codex agents.Fixed along the way, found by two adversarial reviews of this PR.
gate_blockingnow readsouter.host-adapterskipped every check for any command containingaloop bind(Codex too).gate-supervisor-scopelet the supervisor edit source inside a task worktree (all hosts).gate-validate-artifactcrashed withoutCLAUDE_PLUGIN_ROOT.aloop bind <wt>;echo>…is confined (Codex too);gate-supervisor-scoperesolves.., symlinks and relative paths before classifying (all hosts);.envgate switches are ignored;send_message, and the intake ask) is kept as open items in ROADMAP §4.9.Evidence
scripts/test/dsh-runtime.test.mjs: 32/32 checks on the real pinned dsh 0.2.1-rc.2, driven by a scripted Anthropic-SSE model (fixtures/fake-deepseek.mjs), with no network and no tokens. Every escape the reviews reproduced is a check here, including a vanished working directory, a chained bind, andSKIP_HOOKS=1/GATE_RUN_TESTS=0in the project's.env.scripts/test/dsh-plugin.test.mjs: 40/40 in an emulated Cordis context, mission roles and both mission gates included.scripts/test/dsh-install.test.py: 9/9. It covers the recorded owner, another project's re-render and status, a legacy unrecorded profile, worktrees and subdirs,--globaland uninstall.host-adapter13,supervisor-scope26,e2e-live-hosts14,mission-roles33,mission-scope-bash225 commands, plusmodel-routesandcontext-boundary. Each new check was confirmed to fail on the code before its fix. On real dsh, each defence layer also catches the escape alone.make pycheck shellcheck json eval-validate version-check,make test-hostswithALOOP_REQUIRE_DSH=1, andmake conformance78/78 all pass.ALOOP_REQUIRE_DSH=1, so the runtime test cannot silently skip.make e2e-live YES=1 HOST=dsh) needs aDEEPSEEK_API_KEY. It is tracked asBLOCKED_HOSTindocs/TRACKING.md, and the other open dsh items are in ROADMAP §4.9.🤖 Generated with Claude Code
https://claude.ai/code/session_01AiLhAWTRtwbgMCDQNJUbea