8000 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 by adeelahmad · Pull Request #104 · adeelahmad/aloop · GitHub
[go: up one dir, main page]

Skip to content

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
adeelahmad merged 22 commits into
masterfrom
claude/deepseek-host-f3orp6
Oct 20, 2026
Merged

adeelahmad merged 22 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: after #80, DeepSeek Harness (dsh) still had these gaps:

  • one global profile held a single project's models, so another project's roles were refused until it re-ran aloop install dsh;
  • a dsh run had no dollar cost;
  • every role was told to send_message its result and was then denied;
  • live messaging and compaction were unknown;
  • no mission role had run on the real runtime.

Three bugs found during #80's reviews affected every host:

  • an intake that correctly stopped to ask the human was steered 3 rounds and reported BLOCKED;
  • two gates read hook fields with a greedy sed, so a key in the model's arguments could stand in for a host field;
  • a pipefail race in three gates blocked good reports under load (the conformance §6.6 flake), and let a regressed=1 RED 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):

  • per-project models (hosts/dsh/aloop.mjs): a listener on dsh's agent/request waterfall routes each role to aloop 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).
  • cost (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.json gets cost_by_model. An unpriced id gives no cost rather than a guess.
  • capabilities: at a role's start, tools.restrict({deny: ["send_message"]}) removes the tool, so dsh adds no return guidance. The supervisor's send_message reaches a running role at its next step. compaction/end is handled as SessionStart(compact): it records context.compacted and re-injects the handoff. The capability matrix marks dsh live messaging and compaction as advertised.
  • mission roles on the runtime: a market-researcher (on subagent_planning) and a state-assessor (on subagent) run through both mission gates on the real dsh 0.2.1-rc.2.
  • intake NEEDS-HUMAN (gate-intake, intent release|hook|open, hooks.json, the dsh and OpenCode adapters, the playbook): a stop whose contract has only ask questions is released NOT verified, the same way STUCK is. The questions are recorded, and the supervisor is told NEEDS-HUMAN … (not a pass): on dsh as an injected message, on OpenCode in the task result, on Claude Code and Codex as a PostToolUse notice. Malformed or incomplete intakes still block, and gate-dispatch refuses a planner while the newest contract has an open ask.
  • JSON payloads: gate-supervisor-scope and gate-validate-artifact now parse the hook payload with python3. OpenCode's tool_input is built only from each tool's declared arguments.
  • pipe race: validate_comms, gate-red-verify and gate-structural-integrity now 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

  • CI red (Linux and macOS, opencode-plugin NEEDS-HUMAN check): gate-intake found its intake with find | 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. New agentic_find_doc (bin/_finddoc.sh) skips hidden dirs, linked worktrees, node_modules and target, and takes the newest match; gate-standards-cited and 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.
  • Adversarial review (6 lenses, 17 findings). Fixed here, each with a test that fails before:
    • high: OpenCode never ran gate-supervisor-scope on bash (supervisor commits on main, output.md appends, contract rewrites). Pre-existing; now judged with the command's workdir as cwd.
    • medium: a role could forge a NEEDS-HUMAN record whose question text read as a gate verdict in the supervisor's context. intent release now needs the intake's stop and a contract intent new issued in docs/agents/intents/; questions are one quoted line; intent hook reports only its session's records while their contract is newest.
    • low: the dsh/OpenCode release regex read model text (STUCK … NOT verified inside 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.
    • low: "plan only once gate-intake passes" was prose only; gate-dispatch now enforces it (intent open).
    • low: the any-language standards decoy check depended on directory order; it now has a case the old gate fails in any order.
  • The other 11 findings (dsh compaction/title usage missing from cost, sub-cent cost rounding, handoff one step late after an auto-compaction, NUL/surrogate payloads, gate-validate-artifact without python3, two gate-final checks, macOS skipping the dsh runtime test, two conformance hygiene items) are being fixed in a follow-up PR.
  • Master merges (Mission role gates: rounds 3 and 4 review fixes (M9.9, M9.42) #92, Plan approval is a gate: no worker before the human's typed line (M0.9) #99 plan approval, Run 51: cli on master under the plan approval, dependency and secret gates (13/13, $0.94) #108–aloop stop --clear: refuse every host's agent shell (M10.4) #110): the runtime test's second project had no plan approval, so the M0.9 gate rightly blocked its red-worker; it now approves its own sprint 1. SKILL.md is 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).
  • First push (a91b682): all 5 CI jobs green, macOS included.

Suite counts:

  • dsh-runtime 50 (was 32), on the real runtime;
  • dsh-plugin 46 (was 40);
  • dsh-install 10;
  • supervisor-scope 34 (was 26);
  • opencode-plugin 39 (was 25);
  • codex-hooks 19 (was 15);
  • intent-contract 15 (was 6);
  • dispatch 16;
  • any-language 12;
  • e2e-live-hosts 15;
  • context-boundary 4;
  • host-adapter 13.

Not measured: a live DeepSeek run (make e2e-live YES=1 HOST=dsh) still needs a DEEPSEEK_API_KEY.

🤖 Generated with Claude Code

https://claude.ai/code/session_01AiLhAWTRtwbgMCDQNJUbea

claude added 22 commits October 20, 2026 09:01
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
…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
…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
…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
adeelahmad marked this pull request as ready for review October 20, 2026 16:21
@adeelahmad
adeelahmad merged commit f11f5b4 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_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
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