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
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
4aefaf0
Read tool-event gate payloads as JSON, not with sed
claude Oct 20, 2026
52ea566
Release an intake that stops to ask as NEEDS-HUMAN on every host
claude Oct 20, 2026
f77a6b6
dsh: run each role on its own project's models from one profile
claude Oct 20, 2026
706fdaa
Test a mission role on the real dsh runtime
claude Oct 20, 2026
e2ed5aa
Read gate report checks from here-strings, not cut-off pipes
claude Oct 20, 2026
6dbf9c6
Price dsh runs in run-summary from a dated DeepSeek table
claude Oct 20, 2026
203209a
dsh: scope send_message out of roles, prove live messaging and compac…
claude Oct 20, 2026
8b4b4b4
Merge the seven dsh follow-ups (ROADMAP 4.9)
claude Oct 20, 2026
1b69ab7
docs: the dsh follow-ups in ROADMAP 4.9, CHANGELOG, OVERVIEW and the …
claude Oct 20, 2026
86c7d7a
Merge origin/master (#86 doors, #96-#101) into the dsh follow-ups
claude Oct 20, 2026
0caf87d
Gates with no doc named judge the planning doc, never a host's agent …
claude Oct 20, 2026
a91b682
Merge origin/master (#102 targets, run 49) into the dsh follow-ups
claude Oct 20, 2026
7d5182d
OpenCode judges the supervisor's and roles' shell with gate-superviso…
claude Oct 20, 2026
59fb227
A NEEDS-HUMAN release is the intake's own, quoted, and told once on e…
claude Oct 20, 2026
e40a3bf
Merge origin/master (#103-#106) into the dsh follow-ups
claude Oct 20, 2026
cac8db7
gate-dispatch refuses a planner while the newest Intent Contract has …
claude Oct 20, 2026
0b4aaf2
Changelog for the review fixes; the standards decoy test no longer de…
claude Oct 20, 2026
e1c41bb
Merge origin/master (#107 secret scan) into the dsh follow-ups
claude Oct 20, 2026
e9687bb
Merge origin/master (#92, #99 plan approval, #108, #109 aloop stop) i…
claude Oct 20, 2026
518b2df
SKILL.md back under its 50,000-byte budget after the master merge
claude Oct 20, 2026
102e954
Merge remote-tracking branch 'origin/master' into claude/deepseek-hos…
claude Oct 20, 2026
982b4e0
dsh runtime test: the second project gets its own plan approval
claude Oct 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion CHANGELOG.md

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -65,15 +65,15 @@ clippy: ## Clippy with warnings-as-errors
.PHONY: shellcheck
shellcheck: ## Lint gate scripts (skipped with a warning if shellcheck is absent)
@if ! command -v shellcheck >/dev/null; then echo "WARN: shellcheck not installed; skipping (CI enforces it)"; else \
shellcheck -S error -e SC1091 plugin/bin/_gatelib.sh plugin/bin/_failclosed.sh $$(grep -l "^#!/usr/bin/env bash" plugin/bin/gate-*) plugin/bin/log-execution \
shellcheck -S error -e SC1091 plugin/bin/_gatelib.sh plugin/bin/_failclosed.sh plugin/bin/_finddoc.sh $$(grep -l "^#!/usr/bin/env bash" plugin/bin/gate-*) plugin/bin/log-execution \
plugin/bin/selfcheck plugin/bin/transcripts plugin/bin/worktree-create plugin/bin/worktree-remove \
plugin/bin/worktree-hygiene plugin/bin/ensure-tools plugin/bin/md-db plugin/bin/ctx-symbols \
plugin/bin/session-start plugin/bin/_paths.sh plugin/bin/aloop plugin/bin/aladdin plugin/bin/agentic plugin/bin/bind plugin/bin/task-worktree; fi

.PHONY: pycheck
pycheck: ## Byte-compile the Python hook scripts
@python3 -m py_compile plugin/bin/budget plugin/bin/stats plugin/bin/aloop-init plugin/bin/gate-commit-author plugin/bin/_paths.py \
plugin/bin/_usage.py plugin/bin/_events.py plugin/bin/events plugin/bin/intent plugin/bin/lessons plugin/bin/ids plugin/bin/targets plugin/bin/impact plugin/bin/timebox plugin/bin/sweep plugin/bin/capabilities plugin/bin/models plugin/bin/_routes.py plugin/bin/host plugin/bin/_host.py plugin/bin/config plugin/bin/_config.py plugin/bin/layout plugin/bin/_lsp.py scripts/test/fixtures/fake-lsp.py plugin/bin/_timebox.py plugin/bin/host-adapter plugin/bin/install \
plugin/bin/_usage.py plugin/bin/_prices.py plugin/bin/_events.py plugin/bin/events plugin/bin/intent plugin/bin/lessons plugin/bin/ids plugin/bin/targets plugin/bin/impact plugin/bin/timebox plugin/bin/sweep plugin/bin/capabilities plugin/bin/models plugin/bin/_routes.py plugin/bin/host plugin/bin/_host.py plugin/bin/config plugin/bin/_config.py plugin/bin/layout plugin/bin/_lsp.py scripts/test/fixtures/fake-lsp.py plugin/bin/_timebox.py plugin/bin/host-adapter plugin/bin/install \
plugin/bin/_approval.py plugin/bin/mission plugin/bin/_mission.py plugin/bin/_mission_roles.py plugin/bin/_shellread.py plugin/bin/gate-mission-scope plugin/bin/gate-mission-artifact $$(ls plugin/mission/*.py 2>/dev/null) \
&& rm -rf plugin/bin/__pycache__ plugin/mission/__pycache__ && echo "python ok"

Expand Down
4 changes: 2 additions & 2 deletions docs/OVERVIEW.md
< 8BD0 td class="blob-num blob-num-deletion empty-cell">
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,9 @@ The model can ignore a skill; it cannot ignore an exit code.
model; no live DeepSeek run yet).
- CI on every PR: Rust fmt/clippy/tests for both backends, shellcheck on every gate, the
ROADMAP §6 conformance suite (real gates on a fixture crate, plus the focused tests that hold
the tool-level items: 19 of the 20 items, 78 checks; §6.15 is measured by live runs), the host tests
the tool-level items: 19 of the 20 items, 82 checks; §6.15 is measured by live runs), the host tests
(budget/worktree, Codex hooks emulation, supervisor scope, hook matchers, dispatch,
ensure-tools, fail-closed: 73 checks; OpenCode end-to-end: 20; dsh plugin: 40, on the real dsh runtime: 32, dsh install: 9), `claude plugin validate
ensure-tools, fail-closed: 85 checks; OpenCode end-to-end: 31; dsh plugin: 46, on the real dsh runtime: 50, dsh install: 10), `claude plugin validate
--strict`, eval-suite and version-lockstep checks,
the tracking check and `host check`, plus the newer suites (targets render, host swap,
metrics, config, the aladdin CLI, the site).
Expand Down
37 changes: 27 additions & 10 deletions docs/ROADMAP.md

Large diffs are not rendered by default.

16 changes: 13 additions & 3 deletions plugin/README.md
Original file line number Diff line number Diff line change
10000 Expand Up @@ -144,15 +144,25 @@ dsh --profile aloop "Use the aloop skill. Intent: <one line>"
agent presets `aloop-<role>` (each role's prompt is its agent file), `subagent` for the
execution roles and `subagent_planning` for the planning roles, each with its group's model,
and the plugin `hosts/dsh/aloop.mjs`, loaded in place. dsh has no project-scoped plugin
layer, so the profile is global and holds the last-rendered models; the skills go to
layer, so the profile is global and its tools hold the models of the project it was rendered
for (the plugin then routes each role to its own project's models, below); the skills go to
`.dsh/skills` (or `$DSH_HOME/skills` with `--global`).
- **Models**: the supervisor runs on `$ALOOP_DSH_MODEL` (default `deepseek-v4-pro`; dsh has no
`--model` flag); the roles use `deepseek-v4-pro` (strongest/strong) or `deepseek-flash`
(economical) unless `MODEL_*_DSH` is set.
(economical) unless `MODEL_*_DSH` is set. Each role runs on the models of the project the
session is in (the plugin routes its requests to `aloop models resolve <role>`), so one install
serves every project; another project needs `aloop install dsh` there only for its skills.
- **Gates**: every block uses one of dsh's own decision points, so `enforce_gate` is native. A
tool call is denied with the gate's reason; an invalid artifact write comes back as the
gate's feedback; a role's stop gate steers the child back to work before dsh settles it,
up to 3 rounds, and a block past them (or a STUCK release) is injected into the supervisor.
up to 3 rounds, and a block past them (or a STUCK or NEEDS-HUMAN release) is injected into the supervisor.
- **Messages and compaction**: a role is not offered `send_message` (the plugin scopes it out of
the role's tools at its start), so its result reaches the supervisor through its stop gate and
dsh's settlement notice; the supervisor's `send_message` reaches a running role at its next step.
dsh compacts a session in place, and the plugin records `context.compacted` and injects the
handoff again.
- **Cost**: `run-summary --host dsh` prices each logged call from the dated DeepSeek table in
`bin/_prices.py`; a model with no published price leaves the cost unmeasured.
- **Unattended runs**: dsh asks before commands unless `DSH_PERMISSION_MODE=danger-full-access`
(no sandbox, no approvals); use that only in a throw-away repo, as `make e2e-live HOST=dsh` does.
The profile disables dsh's session-log upload (`session-log-deepseek`); set
Expand Down
5 changes: 4 additions & 1 deletion plugin/agents/intake.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,8 @@ Everything is on PATH — never search for it: `selfcheck`, `md-db`, `ctx-symbol
## Hard limits
- Writes no plan and no code.
- Never assumes scope to "keep moving": only `assume`-class questions (safe and
reversible) are assumed, and each is recorded. An `ask`-class question = stop, ask.
reversible) are assumed, and each is recorded. An `ask`-class question = stop, ask:
leave its `answer` empty (never answer it yourself), list it in your report and finish.
Your stop then reaches the supervisor as NEEDS-HUMAN; it asks the human and
re-dispatches you with the answers, which you record.
Never edit an `intent_id` — it is issued by the harness and frozen.
13 changes: 7 additions & 6 deletions plugin/bin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ ctx-symbols) → WARN + grep fallback; never a silent pass.
## Gates (which hook fires each)
| script | event · matcher | checks | exit 2 when |
|--------|-----------------|--------|-------------|
| gate-supervisor-scope | PreToolUse · Write\|Edit\|MultiEdit\|Bash | MAIN agent (no `agent_type`) may not write production source mid-sprint, nor hand-author planning artifacts (stories/tasks/validate/plan/intake/standards.md — dispatch the activity), nor `git commit` on main (commits on main arrive through `task-worktree merge`); sub-agents (`agent_type` present) exempt | supervisor writes source / hand-writes a planner-owned artifact / commits on main |
| gate-dispatch | PreToolUse · Agent\|Task (Codex `spawn_agent`, OpenCode `task`, dsh `subagent`) | the MAIN agent's dispatch of any aloop role passes the project's confirmed model (`aloop models resolve <role>`, matched by family; no model or another model is refused; Codex and OpenCode calls that name none use the agent file's pinned one), and a dispatch of an execution role opens with `TASK_ID: <id>`, that task's contract `<run-data>/state/tasks/<id>.env` exists, and the human approved the sprint's plan as it is now, with STORY_DIR and the task that plan's own (`events approval`, M0.9) (repos with a run-data dir only) | no TASK_ID line / contract not written yet / plan not approved, or changed since |
| gate-supervisor-scope | PreToolUse · Write\|Edit\|MultiEdit\|Bash | MAIN agent (no top-level `agent_type`: the payload is read as JSON, so a key the model puts in `tool_input` is no field) may not write production source mid-sprint, nor hand-author planning artifacts (stories/tasks/validate/plan/intake/standards.md — dispatch the activity), nor `git commit` on main (commits on main arrive through `task-worktree merge`); sub-agents (`agent_type` present) exempt | supervisor writes source / hand-writes a planner-owned artifact / commits on main |
| gate-dispatch | PreToolUse · Agent\|Task (Codex `spawn_agent`, OpenCode `task`, dsh `subagent`) | the MAIN agent's dispatch of any aloop role passes the project's confirmed model (`aloop models resolve <role>`, matched by family; no model or another model is refused; Codex and OpenCode calls that name none use the agent file's pinned one), and a dispatch of an execution role opens with `TASK_ID: <id>`, that task's contract `<run-data>/state/tasks/<id>.env` exists, and the human approved the sprint's plan as it is now, with STORY_DIR and the task that plan's own (`events approval`, M0.9); a planner is refused while the newest Intent Contract has an unanswered `ask` (`intent open`) (repos with a run-data dir only) | no TASK_ID line / contract not written yet / plan not approved, or changed since / an intake's question still open |
| gate-tooling | SubagentStart · exec roles + supervisor self-check | md-db + ctx-symbols installed (execution may not run grep-degraded); runs `ensure-tools --sync` first | a backend is missing and can't be built |
| gate-validate-artifact | PostToolUse · Write | md-db-validate the written artifact | artifact malformed |
| gate-validate-artifact | PostToolUse · Write | md-db-validate the written artifact (`tool_input.file_path`, read as JSON) | artifact malformed / payload not JSON |
| gate-red-verify | SubagentStop · red-worker | worktree-isolated; appended output.md block (validate_comms); every new test FAILS BY ASSERTION; no regression; diff = tests+shims | no/stale output.md block / shared-tree run / a new test passes / prod code / regression |
| gate-scaffold-verify | SubagentStop · scaffolder | worktree-isolated; appended output.md block; one def per symbol (worktree `.aladdin/scaffold-symbols`, or `.agentic/`); panic+TODO; no marked shim; no clobber | no/stale output.md block / shared-tree run / impl body / dup / shim remains |
| gate-green-verify | SubagentStop · green-worker | worktree-isolated; appended output.md block; tests pass; in-scope diff; standards matrix; zero suppressions | no/stale output.md block / shared-tree run / failing test / out-of-scope / suppression |
Expand All @@ -47,7 +47,7 @@ ctx-symbols) → WARN + grep fallback; never a silent pass.
| `worktree-hygiene` | SubagentStart · exec roles — sparse-checks `docs/agents/` out of the new worktree (workers reach it via the absolute `STORY_DIR` back into the main tree, never their own copy) + bootstraps the main tree's `.gitignore` with `.agentic/`/`.transcripts/` if missing. Runs regardless of whether the harness's built-in isolation or `worktree-create` made the worktree. |
| `worktree-create` | OPTIONAL `git worktree add` helper — wire into **user `settings.json`** as a `WorktreeCreate` hook for a non-git VCS (NOT a valid plugin-manifest event). Built-in `isolation: "worktree"` covers plain git. Also applies the same sparse-checkout + `.gitignore` bootstrap as `worktree-hygiene`. |
| `worktree-remove` | OPTIONAL `git worktree remove` cleanup companion for the above (user `settings.json` `WorktreeRemove`) |
| `run-summary` | `<run-dir> [--intent] [--host claude|codex|opencode] [--checks N/M] [--secs] [--plugin]` — a live run's measured record: `summary.json`, `summary.md` and a `docs/evidence/README.md` row from the session's `result` lines and `gates.jsonl` (cost, wakes, plugin reads, blocks); called by `e2e-live.sh` |
| `run-summary` | `<run-dir> [--intent] [--host claude|codex|opencode|dsh] [--checks N/M] [--secs] [--plugin]` — a live run's measured record: `summary.json`, `summary.md` and a `docs/evidence/README.md` row from the session's `result` lines and `gates.jsonl` (cost, wakes, plugin reads, blocks); on dsh, which prints no cost, every call its plugin logged is priced from `_prices.py` (`cost_by_model`; an unpriced model leaves the cost unmeasured); called by `e2e-live.sh` |
| `log-execution` | `wave-K activity SN-NN transition [detail…] [execution.log]` — append one transition line (default ledger: the latest sprint's); `--help` |
| `ensure-tools` | SessionStart — builds md-db + ctx-symbols into `$CLAUDE_PLUGIN_DATA/bin` on first run / version change (background), tells the model where the tools are. `--sync` build now · `--wait` · `--resolve T` real binary path |
| `md-db`, `ctx-symbols` | PATH shims (this `bin/` is on PATH in every session): exec the real binary, building it once if needed. Gates test presence with `ensure-tools --resolve`, never `command -v` |
Expand All @@ -59,13 +59,14 @@ ctx-symbols) → WARN + grep fallback; never a silent pass.
| `task-worktree` | `add\|path\|remove <TASK_ID>` — per-task worktree at `.agentic/worktrees/<TASK_ID>` (branch `agentic/<TASK_ID>`) for hosts without built-in isolation; `merge <TASK_ID> <BRANCH>` — `--no-ff` merge of a host-made branch that emits `worktree.merged` + `commit.created` (the lineage on Claude Code) |
| `bind` | a worker's first command on Codex/OpenCode: validates its worktree (`aloop bind <path>`) |
| `_usage.py` | token usage from any host's log: Claude transcripts, Codex rollouts (`.jsonl`/`.zst`, cumulative totals → per-call rows), the OpenCode plugin's usage log |
| `_prices.py` | the dated, sourced DeepSeek price table (USD per 1M tokens: cache hit, cache miss, output; peak and off-peak by UTC time) that `run-summary` prices a dsh run with; an id it lacks is unpriced, never guessed |
| `aloop-init` | project setup: `--show` the defaults for the human, `--apply KEY=VALUE…` writes `docs/agents/defaults.md`, the managed `.gitignore` lines and the repo-local git author; `--ensure-gitignore` (SessionStart, worktree-hygiene) re-adds them |
| `session-start` | SessionStart — re-applies the managed `.gitignore` lines (if init opted in) and loads `docs/agents/NEXT.md` into the context (the post-`/clear` handoff) |
| `gate-commit-author` | PreToolUse · Bash — a `git commit` must use the configured author: denies `--author`/`-c user.*`/`GIT_AUTHOR_*` overrides, a mismatched repo identity, and Claude co-author/attribution trailers (unless `CLAUDE_COAUTHOR=yes`) |
| `stats` | `hook` (Stop + SubagentStop): rewrites `.agentic/ledger/<session>.jsonl` from the transcripts (tokens per API call, main + sub-agents) and, once after a sprint closed, shows the totals + tells the supervisor to ask for `/clear`. `sprint-close` (run by gate-final on pass): `sprintN/stats.md` + `NEXT.md`. `show`: session / sprint / project totals |
| `budget` | per-attempt time box for red/scaffolder/green workers. `hook pre\|post\|stop` (PreToolUse/PostToolUse `*`, SubagentStop): soft time limit → warning in the worker's tool result; soft token limit → telemetry only (`task.token_soft_limit` in `.agentic/logs/events.jsonl`, surfaced in stats.md/NEXT.md, worker not told); hard limit → PostToolUse `continue: false` stops the worker on that call, later calls refused; the harness (not the worker) appends the `status: re-plan` output.md block and records `task.token_hard_limit`/`task.time_hard_timeout` (+ the worktree's `.aladdin/budget-exceeded`, so its gate releases the stop unverified). `resolve` · `watch` (supervisor sleep; exits with STOPPED / KILL / STUCK lines) · `status` · `mark-killed` · `replans` (split cap) |
| `events` | the `aladdin.event/1` stream in `.agentic/logs/events.jsonl` (`_events.py`): `emit EVENT --task … --parent …` · `query --task/--event/--sprint` · `lineage TASK_ID` (worktree → limits → gates → decisions → merged/discarded, from refs, never transcripts) · `trace SHA` (commit → task → gates → plan → intent → approver) · `commit-msg TASK` (title + decisions + ids) · `graph [--format json|dot]` (downstream export) · `authority … --sprint N` (the human's grant: exits 3 with the `approve SN <code>` / `autonomous SN <code>` line to type, M0.9) · `approval SPRINT|TASK_ID [--story-dir D] [--role R]` (exit 0 approved, 2 not; `gate-dispatch` runs it). `plan.approved` and `human.approval` cannot be emitted. Written by `budget`, every gate verdict (`_gatelib.sh`) and `task-worktree`. |
| `intent` | the internal Intent Contract (ROADMAP §4.1): `new` issues the canonical `intent_id` + skeleton under `docs/agents/intents/`; `check FILE [--mode reviewed\|autonomous]` validates fields and the clarification policy (exit 2 invalid, 3 an `ask` needs the human); `summary FILE` lists defaults/assumptions/spikes for approval. Run by `gate-intake`. |
| `intent` | the internal Intent Contract (ROADMAP §4.1): `new` issues the canonical `intent_id` + skeleton under `docs/agents/intents/`; `check FILE [--mode reviewed\|autonomous]` validates fields and the clarification policy (exit 2 invalid, 3 an `ask` needs the human); `summary FILE` lists defaults/assumptions/spikes for approval. Run by `gate-intake`; `release FILE` (gate-intake, on a host's stop with exit 3) records a NEEDS-HUMAN release in `<run-data>/state/needs-human/<intent_id>.json` + `gate.failed` (`released=needs-human`); `hook` (PostToolUse `*`) tells the supervisor each release once, on Claude Code and Codex; `open` lists the newest contract's unanswered asks, quoted (exit 3; run by `gate-dispatch` before a planner). |
| `ids` | canonical identity + referential integrity (ROADMAP §4.5): `freeze SPRINT_DIR` checks the structured references (story frontmatter ↔ dir, dirs ↔ stories.md, validate.md ↔ tasks.md headings, `split_from` → a retired task) and issues frozen UUIDv7 ids in `sprintN/ids.json`; a retired alias is never reused · `check` · `resolve ALIAS`. Run by `gate-stage2-complete`; events then carry `story_uid`/`task_uid`. |
| `deps` | supply chain (ROADMAP M10.2): `added --base REF` lists the dependencies a change adds to Cargo.toml, package.json, requirements*.txt, pyproject.toml or go.mod · `unplanned --base REF --story DIR` the ones the story's plan does not name (exit 2); gate-green-verify blocks on them. No third-party parser. |
| `stop` | one stop for the whole project (ROADMAP M10.4): `aloop stop [REASON]` sets the stop flag (`.aladdin/state/STOP`) and records `run.stopped`; while it is set `budget hook pre`, which every host runs on every tool call, refuses each agent's acting calls and ends it at its next read, and `mission tick` answers STOPPED. `--status` (exit 3 when stopped). `--clear` resumes and records `run.resumed`, from the human's own terminal only (a shell an agent host runs, `CLAUDECODE`, `DSH_SHELL`, `CODEX_THREAD_ID` or `ALOOP_AGENT_SHELL` set, is refused). |
Expand All @@ -87,7 +88,7 @@ done) AND a `SubagentStop` hook backstop running the same gate:

| role | gate | event · matcher |
|---|---|---|
| intake | `gate-intake` (five-part intent present) | SubagentStop · `intake` |
| intake | `gate-intake` (five-part intent present; a valid Intent Contract. Unanswered `ask` questions alone release a host's stop at once as NEEDS-HUMAN, not a pass; a self-check still fails) | SubagentStop · `intake` |
| standards | `gate-standards-cited` | SubagentStop · `standards` |
| planner | `gate-stage2-complete` (auto-discovers sprint dir) | SubagentStop · `planner` |
| archivist | `gate-memory` (valid + never relaxes an invariant) | SubagentStop · `archivist` |
Expand Down
Loading
Loading
0