8000 DeepSeek Harness (dsh) as a fourth host by adeelahmad · Pull Request #80 · adeelahmad/aloop · GitHub
[go: up one dir, main page]

Skip to content

DeepSeek Harness (dsh) as a fourth host - #80

Merged
adeelahmad merged 8 commits into
masterfrom
claude/deepseek-host-f3orp6
Oct 20, 2026
Merged

adeelahmad merged 8 commits into
masterfrom
claude/deepseek-host-f3orp6

Conversation

@adeelahmad
@adeelahmad adeelahmad commented Oct 20, 2026 •
Copy link
Copy Markdown
Owner

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 (alias deepseek) renders a dsh --profile aloop profile, and the loop runs on dsh with the same gates as on Claude Code. A plugin running inside dsh does three things:

  • it denies a tool call before it runs;
  • it blocks an invalid artifact after it is written;
  • it steers a stopping role back up to 3 times, after which the supervisor is told the role is BLOCKED (or STUCK, released unverified).

Roles are dispatched as agent presets on two subagent tools, one per model route: deepseek-v4-pro for planning and deepseek-flash for work by default. MODEL_PLANNING_DSH / MODEL_WORKER_DSH override them. The mission layer's six roles run on dsh too, with gate-mission-scope on every write and command and gate-mission-artifact at each role's start and stop. The profile records the project it serves (or --global), so another project's aloop-init never re-renders it, and a session in another project is told to run aloop install dsh --project <it>.

How:

  • plugin/hosts/dsh/aloop.mjs maps dsh's plugin events onto the existing Claude-shaped gate scripts:

    dsh event Claude-shaped hook
    tools/pre-execute PreToolUse
    tools/post-execute PostToolUse
    agent/created SessionStart / SubagentStart
    agent/pre-step UserPromptSubmit
    child agent/turn-stopping SubagentStop
    root agent/turn-stopping Stop
    session/event usage usage JSONL

    No gate is reimplemented in JS. A gate that cannot run denies the call (fail closed). tool_input is 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 bind moves a writer's working directory into its worktree. host-adapter confines dsh writers there: bash's own workdir, a cd out 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 directory ensure() returns). Reads stay unconfined.

  • Supporting changes: routes, capabilities, a Dsh adapter in _host.py (enforce_gate: native), config keys, aloop-init, skills and README docs, and make e2e-live HOST=dsh. The last exits 2 with the fix when no DEEPSEEK_API_KEY is 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.

  • First review (24 agents; 13 confirmed, 2 refuted):
    • OpenCode's gate_blocking now reads outer.
    • host-adapter skipped every check for any command containing aloop bind (Codex too).
    • gate-supervisor-scope let the supervisor edit source inside a task worktree (all hosts).
    • gate-validate-artifact crashed without CLAUDE_PLUGIN_ROOT.
  • Second review (3 lenses, 15 agents; 11 confirmed and fixed, 1 refuted):
    • the bind-only rule now takes a strict word class, so aloop bind <wt>;echo>… is confined (Codex too);
    • gate-supervisor-scope resolves .., symlinks and relative paths before classifying (all hosts);
    • .env gate switches are ignored;
    • an interrupted root Stop injects its notice;
    • the installer's one global profile keeps its owner;
    • the live smoke's seed init no longer re-renders host profiles.
    • The refuted finding (return guidance naming a denied 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, and SKIP_HOOKS=1 / GATE_RUN_TESTS=0 in 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, --global and uninstall.
  • Other suites that gained cases: host-adapter 13, supervisor-scope 26, e2e-live-hosts 14, mission-roles 33, mission-scope-bash 225 commands, plus model-routes and context-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.
  • Local runs after merging master (Mission loop drives the goal commands (M9.28) #87, README, brand kit and repo front page for aloop #88, targets: two small tasks on the same files are one task #90, Mission loop end to end offline (M9.13); a baseline binds the direction (M9.25); ethics rows in the ledger (M9.24) #91): make pycheck shellcheck json eval-validate version-check, make test-hosts with ALOOP_REQUIRE_DSH=1, and make conformance 78/78 all pass.
  • CI: the hosts job installs the pinned dsh and sets ALOOP_REQUIRE_DSH=1, so the runtime test cannot silently skip.
  • Not measured yet: a live DeepSeek run (make e2e-live YES=1 HOST=dsh) needs a DEEPSEEK_API_KEY. It is tracked as BLOCKED_HOST in docs/TRACKING.md, and the other open dsh items are in ROADMAP §4.9.

🤖 Generated with Claude Code

https://claude.ai/code/session_01AiLhAWTRtwbgMCDQNJUbea

`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
@adeelahmad adeelahmad self-assigned this Oct 20, 2026
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
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
adeelahmad marked this pull request as ready for review October 20, 2026 08:20
@adeelahmad
adeelahmad merged commit 227bc7d into master Oct 20, 2026
5 checks passed
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

0