Repository navigation
dsh follow-ups: per-project models, cost, capabilities, mission roles on the runtime; intake NEEDS-HUMAN, JSON payloads and a gate pipe race on every host - #104
Merged
Conversation
gate-supervisor-scope and gate-validate-artifact read each hook field with a greedy sed over the raw payload, which took the last "<name>": "<value>" anywhere in it. A key the model added to its arguments stood in for the host's field (an agent_type exempted the supervisor, a cwd moved its shell into a task worktree so its commit on main went unjudged), a later field's file_path won, and an escaped quote cut a path short (a source write was judged as docs/agents/.../init.md; a planning write went unvalidated). Both gates now parse the payload with python3: the host's fields from the top level, the path and command from tool_input. A payload that is not a JSON object blocks (gate-supervisor-scope only while the repo has a sprint lock, as before for crashes). The OpenCode plugin built tool_input by spreading the model's arguments; OpenCode 1.19.3 hands tool.execute.before the raw AI SDK input (no schema validation), so a file_path argument beside filePath hid the path OpenCode writes from gate-supervisor-scope and gate-mission-scope. tool_input is now built from each tool's declared arguments, Claude-shaped, as the dsh plugin does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AiLhAWTRtwbgMCDQNJUbea
gate-intake blocked a stop while the Intent Contract had an unanswered `ask` question (intent check exit 3). The intake cannot answer it, so a correct intake was steered 3 rounds and reported BLOCKED on dsh and OpenCode, and blocked up to Claude Code's own 8-block cap. A host's SubagentStop whose only problem is unanswered `ask` questions is now released at once, NOT verified, as a STUCK release is: `intent release` records the questions in <run-data>/state/needs-human/ and emits gate.failed (released=needs-human), and the gate prints a NEEDS-HUMAN marker. The dsh and OpenCode plugins read the marker (one release regex with STUCK) and tell the supervisor NEEDS-HUMAN with the questions; on Claude Code and Codex a PostToolUse hook (`intent hook`) puts the same notice on the supervisor's next tool result, once, while the questions are still open. A self-check, a direct run, a malformed contract and an incomplete five-part intake still block. The skill tells the supervisor to ask the human and re-dispatch intake with the answers. Tests: intent-contract, codex-hooks (Codex and Claude Code shapes), dsh-plugin, opencode-plugin, and dsh-runtime on the real dsh runtime. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AiLhAWTRtwbgMCDQNJUbea
The one global dsh profile pinned the role models of the project it was rendered for, so in any other project gate-dispatch refused every role until `aloop install dsh` was run there. The plugin now routes each role's requests through dsh's agent/request waterfall to the model its own project resolves (`aloop models resolve <role>` in the session's project; an empty answer is the supervisor's route, as a child inherits it), and the dispatch reports that model to gate-dispatch. A role whose model cannot be resolved is refused at dispatch and its request fails, never falling back to another project's model. The wrong-project warnings that no longer apply are updated: the session note fires only when the project has no aloop skills for dsh, `install status` and `aloop-init` say the profile's roles run on this project's models, and gate-dispatch drops its dsh re-install hint. The owner bookkeeping (only the project a profile serves re-renders or removes it) is unchanged. Proven on the real pinned dsh 0.2.1-rc.2: a second project with swapped MODEL_*_DSH and no install of its own runs its workers and planning roles on its models while the first keeps its own. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AiLhAWTRtwbgMCDQNJUbea
The runtime test now dispatches two mission roles beside the red-worker and intake: a market-researcher through subagent_planning and a state-assessor through subagent. It checks each one runs on its route's model and that gate-mission-artifact writes the start record. It checks that gate-mission-scope denies a write to source and a dsh or claude session started from a tool call (stub host CLIs show nothing ran). A role that writes a sourced research report with the harness's own commands passes its stop gate at once (artifact.created). A role that writes nothing is steered back 3 times, then reported BLOCKED. Each new check fails when its wiring is removed: gate-mission-scope from the pre-execute checks, the start call in agent/created, the mission stop gate in childStop, or the planning route's model in the profile. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AiLhAWTRtwbgMCDQNJUbea
The conformance 6.6 flake ("latest output.md block missing '### Next'" on
the `exit 0` tamper case) was not the fixture: each case writes a fresh
output.md holding one block, so the latest block was never ambiguous. The
gates tested text with `printf|echo "$x" | grep -q` under `set -o
pipefail`. bash writes a multi-line printf/echo in several write()s
(strace: the block's last line "next" is its own write after "### Next");
grep -q exits at its match, the writer dies of SIGPIPE (PIPESTATUS 141 0),
and pipefail failed the check. Under CPU load the tamper case's gate
false-blocked 5 times in 1200 runs (3x '### Next', 1x Summary, 1x Result).
The same race decided three more verdicts, and text longer than a pipe
holds made each certain: a long RED report read as "no FAILing test row",
a RED reporting regressed=1 ahead of a long section passed, and a HIGH
structural finding ahead of a long findings list passed.
validate_comms, gate-red-verify and gate-structural-integrity now read the
text from here-strings, with no writer to cut off. Four conformance checks
drive each case with a report or findings list past twice a pipe's size
(fail before, pass after); the tamper case's gate then passed 1200 of 1200
runs under the same load.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AiLhAWTRtwbgMCDQNJUbea
dsh prints no cost line, so a dsh run's summary reported tokens and no dollars. Its plugin already logs every call's exact usage per role under <run-data>/host/dsh; run-summary --host dsh now prices each call from plugin/bin/_prices.py (DeepSeek's published USD per 1M tokens, read 2026-10-20: cache hit, cache miss and output, peak or off-peak by the call's UTC time) and records cost_by_model beside the total. A model with no published price keeps its tokens with usd null, and the run's cost is then null (the row reads —), never a guess or a partial sum. No usage log is no measurement either. Tests: e2e-live-hosts (a fixture run dir gives the exact dollars per model, the legacy flash id at the Flash price, an unknown id gives null) and dsh-runtime (the real dsh 0.2.1-rc.2 logs are priced per the model ids dsh sent). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AiLhAWTRtwbgMCDQNJUbea
…tion dsh 0.2.1-rc.2 appends "send your result to the parent with send_message" to a child's task whenever the child can see send_message (dsh-subagent startLocal), and the plugin denies that call, so every role spent one denied call per dispatch. The plugin now restricts send_message out of each role's own tools at agent/created (tools.restrict on the agent's scope, agent-local), which runs before dsh's check, so the guidance never comes. The supervisor keeps the tool; the pre-execute deny stays as defence in depth. A role's closing text still reaches the supervisor in dsh's settlement notice. dsh compacts a session in place (automatically near its context limit, on a context overflow, or /compact) and records compaction/start, summary and end session events, but never re-announces the agent. The plugin now treats a committed compaction/end of the supervisor's session as Claude Code's SessionStart(compact): session-start records context.compacted and the handoff and tools note are injected again. The real-runtime test (fake-deepseek can now hold a step until another role reaches one, address a child by id, and fail once with a context overflow, answering dsh's compaction request with a checkpoint) proves: roles are offered no send_message and get no return guidance; the supervisor's send_message reaches a running role at its next step; the time soft-stop reaches a running worker mid-turn; dsh compacts an overflowed session and the plugin records it and restores the handoff. The capability matrix now advertises compaction and live messaging for dsh, and the dsh adapter names send_message as its send vocabulary. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AiLhAWTRtwbgMCDQNJUbea
- conformance-66: gate report checks read here-strings, not cut-off pipes - json-payload: tool-event gates parse the hook payload as JSON; OpenCode builds tool_input from declared arguments - intake-ask: an intake that stops to ask is released at once as NEEDS-HUMAN - per-project-models: each dsh role runs on its own project's models - dsh-capabilities: roles lose send_message; live messaging and compaction - mission-runtime: two mission roles on the real dsh runtime - dsh-cost: run-summary prices dsh calls from a dated DeepSeek table Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AiLhAWTRtwbgMCDQNJUbea
…dsh notes Closes nine ROADMAP 4.9 items, adds the gaps the follow-ups found as new items, and updates the suite counts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AiLhAWTRtwbgMCDQNJUbea
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AiLhAWTRtwbgMCDQNJUbea
…file gate-intake, gate-standards-cited and standards_file found intake.md and standards.md with `find | head -1` (or a locale-dependent sort), so in an OpenCode project they could judge .opencode/agents/intake.md. On CI's file order the Intent Contract was never checked and the OpenCode NEEDS-HUMAN check failed (Linux and macOS). agentic_find_doc (bin/_finddoc.sh) skips hidden dirs, linked worktrees, node_modules and target, and takes the newest match. Tests: intent-contract and any-language decoys, both failing before the fix. Also: mission-core's "another code" case appended "ff" to the 8-digit code, a valid longer sha prefix 1 run in 256; it now appends digits that never continue the sha. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AiLhAWTRtwbgMCDQNJUbea
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AiLhAWTRtwbgMCDQNJUbea
…r-scope The OpenCode plugin queued gate-supervisor-scope only for write, edit and apply_patch, so on OpenCode alone the supervisor could commit on main or append an agent's output.md block during a sprint, and a role could rewrite its task contract with sed. bash now runs the gate too, with the payload cwd set from the command's workdir (a commit in a task worktree is a worker's). Tests: three refusals and the worktree pass, failing before the change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AiLhAWTRtwbgMCDQNJUbea
…very host Review findings on the intake release: - intent release now needs the intake's SubagentStop and a contract that intent new issued (intent.received) in docs/agents/intents/, so a role can no longer forge a record from a file it wrote. - The notice quotes each question on one line (ASK lines too): an intake's text can no longer start a line in the gate's voice. - intent hook reports only the session's own records, and only while the contract is still the newest one gate-intake judges. - dsh and OpenCode read the release from the gate's own marker line (the last, at a line start) and accept NEEDS-HUMAN only at the intake's stop; a question quoting 'STUCK ... NOT verified' no longer reads as STUCK. They tell the supervisor through intent hook, which claims the record, so a later Claude Code or Codex session is not told again. Tests: intent-contract (forged files, another role, sessions, a newer contract, one gate line), dsh-plugin and opencode-plugin (a STUCK-quoting question, the record claimed), all failing before the change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AiLhAWTRtwbgMCDQNJUbea
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AiLhAWTRtwbgMCDQNJUbea
…an open ask "Plan only once gate-intake passes" was prose only. A NEEDS-HUMAN release passes the intake's stop unverified, and gate-dispatch admitted the planner after its model check, so a supervisor that missed the notice, or had not had it yet (on Claude Code it rides a PostToolUse, which comes after the planner's call when the intake ran in the background), planned from an intent with no intent.normalized. - intent open (new) prints the newest contract's unanswered ask questions (the contract gate-intake judges), one quoted line each as the notice does, and exits 3 when any are open; 0 with none, or no contract. The notice and open share one quoting helper. - gate-dispatch refuses a planner dispatch on exit 3, naming the questions and saying to ask the human and re-dispatch intake. The intake, standards and every other role are unaffected, and a project with no contract plans as before. - ROADMAP 4.9: an invalid newest contract (exit 2) still admits a planner; recorded as an open item. Tests: dispatch.test.py (16) and intent-contract.test.py (15), both failing before the change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AiLhAWTRtwbgMCDQNJUbea
…pends on file order any-language 4c passed on the old find | head -1 gate whenever the directory listing put docs/ first. It now also runs the gate with only a host's agent file present, which the old gate judged and blocked on in any order. The intent hook help says dsh and OpenCode run it too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AiLhAWTRtwbgMCDQNJUbea
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AiLhAWTRtwbgMCDQNJUbea
…nto the dsh follow-ups Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AiLhAWTRtwbgMCDQNJUbea
Master's plan-approval lines and this branch's NEEDS-HUMAN line together made 50,113 bytes. The NEEDS-HUMAN line moves out: the notice itself and gate-dispatch's planner refusal say what to do, and reference.md has the full step. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AiLhAWTRtwbgMCDQNJUbea
Master's plan approval gate (M0.9) admits no worker before the human's go, and that go is recorded per project. The second project in the runtime test had none, so gate-dispatch rightly blocked its red-worker. It now approves its own sprint 1 the way the first project does. 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 16:21
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_01WBVFNrEg85XAnSfHQidCXN # Conflicts: # plugin/bin/README.md
adeelahmad
pushed a commit
that referenced
this pull request
Oct 20, 2026
… into the artifact gate fixes Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0197nSutKR1QAAqgJhFZ8fPj
adeelahmad
pushed a commit
that referenced
this pull request
Oct 20, 2026
…oop stop) into the M9.31 branch Conflicts: both sides' plugin constants and helpers kept (the door gate's timeout and read-only set beside master's NEEDS-HUMAN release), the dsh test's question helper renamed (master added a child named `ask`). This branch's open items move to M9.56–M9.65: master took M9.55 and #114 claims M9.48. 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: after #80, DeepSeek Harness (dsh) still had these gaps:
aloop install dsh;send_messageits result and was then denied;unknown;Three bugs found during #80's reviews affected every host:
sed, so a key in the model's arguments could stand in for a host field;pipefailrace in three gates blocked good reports under load (the conformance §6.6 flake), and let aregressed=1RED or a HIGH structural finding through.After: those seven ROADMAP §4.9 items are closed, nine items in all, each with a test that fails on the old code. One item is per-host follow-up wiring. The new gaps found along the way are ROADMAP items.
How (one commit per item, folded into one merge):
hosts/dsh/aloop.mjs): a listener on dsh'sagent/requestwaterfall routes each role toaloop models resolve <role>in the session's project. The dispatch reports that model to gate-dispatch. If the model can't be resolved, the dispatch is refused and the request fails (fail closed).bin/_prices.py,run-summary --host dsh): a table taken from https://api-docs.deepseek.com/quick_start/pricing, read 2026-10-20, covering cache hit, cache miss and output, with peak and off-peak rates.summary.jsongetscost_by_model. An unpriced id gives no cost rather than a guess.tools.restrict({deny: ["send_message"]})removes the tool, so dsh adds no return guidance. The supervisor'ssend_messagereaches a running role at its next step.compaction/endis handled as SessionStart(compact): it recordscontext.compactedand re-injects the handoff. The capability matrix marks dsh live messaging and compaction asadvertised.subagent_planning) and a state-assessor (onsubagent) run through both mission gates on the real dsh 0.2.1-rc.2.gate-intake,intent release|hook|open,hooks.json, the dsh and OpenCode adapters, the playbook): a stop whose contract has onlyaskquestions is released NOT verified, the same way STUCK is. The questions are recorded, and the supervisor is toldNEEDS-HUMAN … (not a pass): on dsh as an injected message, on OpenCode in thetaskresult, on Claude Code and Codex as a PostToolUse notice. Malformed or incomplete intakes still block, andgate-dispatchrefuses a planner while the newest contract has an open ask.gate-supervisor-scopeandgate-validate-artifactnow parse the hook payload with python3. OpenCode'stool_inputis built only from each tool's declared arguments.validate_comms,gate-red-verifyandgate-structural-integritynow read here-strings (grep -q … <<<"$x"), so there is no writer for grep to cut off.CI failure on the first push, and the review
gate-intakefound its intake withfind | head -1, and on the runners' file order that was OpenCode's own.opencode/agents/intake.md, whose dir holds no contract, so the open ask passed unchecked. Newagentic_find_doc(bin/_finddoc.sh) skips hidden dirs, linked worktrees,node_modulesandtarget, and takes the newest match;gate-standards-citedand the gate-matrix lookup use it too. Decoy tests fail on the old code. A 1-in-256 flake in master's mission-core test is fixed too.gate-supervisor-scopeonbash(supervisor commits on main,output.mdappends, contract rewrites). Pre-existing; now judged with the command'sworkdiras cwd.intent releasenow needs the intake's stop and a contractintent newissued indocs/agents/intents/; questions are one quoted line;intent hookreports only its session's records while their contract is newest.STUCK … NOT verifiedinside a question); now the gate's own marker line, NEEDS-HUMAN only at the intake's stop, and the record is claimed so no later session is told again.gate-dispatchnow enforces it (intent open).gate-validate-artifactwithout python3, twogate-finalchecks, macOS skipping the dsh runtime test, two conformance hygiene items) are being fixed in a follow-up PR.SKILL.mdis back under its 50,000-byte budget (49,995) after both sides added lines.Evidence
Local on 982b4e0, with
DSH_BIN+ALOOP_REQUIRE_DSH=1:make shellcheck pycheck json eval-validate version-check: pass.make test-hosts: pass.make conformance: 82/82, in fallback mode (no Rust tools on PATH, as on CI).Suite counts:
dsh-runtime50 (was 32), on the real runtime;dsh-plugin46 (was 40);dsh-install10;supervisor-scope34 (was 26);opencode-plugin39 (was 25);codex-hooks19 (was 15);intent-contract15 (was 6);dispatch16;any-language12;e2e-live-hosts15;context-boundary4;host-adapter13.Not measured: a live DeepSeek run (
make e2e-live YES=1 HOST=dsh) still needs aDEEPSEEK_API_KEY.🤖 Generated with Claude Code
https://claude.ai/code/session_01AiLhAWTRtwbgMCDQNJUbea