8000 GREEN's dependency check reads only the plan text the human's grant pins (M0.9, M10.2) by adeelahmad · Pull Request #113 · adeelahmad/aloop · GitHub
[go: up one dir, main page]

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ tag in lockstep. Tag releases as `vMAJOR.MINOR.PATCH`.
## [Unreleased]

### Added
- Plan approval is a gate (ROADMAP M0.9): `gate-dispatch` starts no execution role (RED, SCAFFOLD, GREEN, structural review, final gate) of a sprint until the human approved that sprint's plan as it is now. `events authority --sprint N` exits 3 with the line to type, `approve SN <code>` (reviewed, the code naming the sha256 of the sprint's plan files) or `autonomous SN <code>` (autonomy for that sprint, re-plans included, the code naming the authority record); the task contract's `STORY_DIR` and task must be a live story and task of that plan's frozen `ids.json`; the line must be among the human's captured prompts and typed after the sprint's last decision (M9.17's mechanism). A changed plan, a re-plan in reviewed mode, a roll-back to an old line, another sprint's line, a negated line and a hand-edited or emitted record all block (`plan.approved` and `human.approval` cannot be `events emit`ted); an approved autonomous mission grants its own sprints autonomy with no line. `events approval SPRINT|TASK_ID` is the check. Codex's `spawn_agent` (`agent_type`, `message`) and OpenCode's `task` now reach `gate-dispatch` too, and `claude -p`, `codex exec` and `opencode run` from a tool call are blocked in every aloop project, not only where a mission is set. `e2e-live.sh` pre-approves with an `autonomous S1 <code>` line in its prompt. Reason: the north star allows no human help beyond approval, and the approval was a playbook rule a supervisor could skip. A 4-lens adversarial review found 9 defects: 7 fixed with a check red on the first head (cross-sprint or unplanned `STORY_DIR`, a retyped line kept stale, mission autonomy over a non-mission sprint, `transcripts prune` dropping the prompts grants cite, `codex plugin add` refused, a code past column 200, a crash on a malformed run-log line), 2 in the playbook (intake reuses a mission's handed-off intent; a reviewed re-plan waits for the human's new line). `transcripts prune` now keeps the human's prompts. Test: `plan-approval.test.py` (20 checks, red on master), plus the host suites.
- Plan approval is a gate (ROADMAP M0.9): `gate-dispatch` starts no execution role (RED, SCAFFOLD, GREEN, structural review, final gate) of a sprint until the human approved that sprint's plan as it is now. `events authority --sprint N` exits 3 with the line to type, `approve SN <code>` (reviewed, the code naming the sha256 of the sprint's plan files, its intake and standards included) or `autonomous SN <code>` (autonomy for that sprint, re-plans included, the code naming the authority record); the task contract's `STORY_DIR` and task must be a live story and task of that plan's frozen `ids.json`; the line must be among the human's captured prompts and typed after the sprint's last decision (M9.17's mechanism). A changed plan, a re-plan in reviewed mode, a roll-back to an old line, another sprint's line, a negated line and a hand-edited or emitted record all block (`plan.approved` and `human.approval` cannot be `events emit`ted); an approved autonomous mission grants its own sprints autonomy with no line. `events approval SPRINT|TASK_ID` is the check. Codex's `spawn_agent` (`agent_type`, `message`) and OpenCode's `task` now reach `gate-dispatch` too, and `claude -p`, `codex exec` and `opencode run` from a tool call are blocked in every aloop project, not only where a mission is set. `e2e-live.sh` pre-approves with an `autonomous S1 <code>` line in its prompt. Reason: the north star allows no human help beyond approval, and the approval was a playbook rule a supervisor could skip. A 4-lens adversarial review found 9 defects: 7 fixed with a check red on the first head (cross-sprint or unplanned `STORY_DIR`, a retyped line kept stale, mission autonomy over a non-mission sprint, `transcripts prune` dropping the prompts grants cite, `codex plugin add` refused, a code past column 200, a crash on a malformed run-log line), 2 in the playbook (intake reuses a mission's handed-off intent; a reviewed re-plan waits for the human's new line). `transcripts prune` now keeps the human's prompts. Test: `plan-approval.test.py` (20 checks, red on master), plus the host suites. The approved plan's files include the sprint's `intake.md` and `standards.md`, and a grant pins them file by file: GREEN's check for an unplanned dependency reads only the text the human's grant pins, the plan they approved or, under their autonomy grant, the plan as the planner's gate last passed it (`gate-stage2-complete` records `plan.gated` on the planner's stop), as it was then, so text added later names nothing and a re-plan does not undo an approved dependency.
- Core agent principles and the one-way / two-way door rule (ROADMAP §4.12, M9.30): seven core principles from Amazon's Leadership Principles, the door rule first, go to every aloop agent at session start (`aloop mission principles show --core`). A two-way door (a branch, a local edit, a test, a draft PR) is decided, not asked. A one-way door (`aloop mission doors list`: a push or merge to the default branch, a force-push, a release, a deploy, a message sent outside, a secret, dropped data, …) is stopped by a PreToolUse gate with `ASK[door] <id>` until the human records an authorization in their own terminal (`aloop mission doors authorize ID --by NAME --reason TEXT [--until DATE] [--once DIGEST]`, a hash-chained ledger row); a wall (switching a gate off, forging a human record) is never crossed. While a run is live, a question to the human must name a door the gate stopped and nobody authorized. Reason: Adeel's request that agents decide two-way doors themselves and ask only at a one-way door not already authorized. Test: `mission-doors.test.py`. Hardened by an adversarial review (15 findings, each a test): the gate reads Bash as the shell does (comments, line continuations, heredoc bodies, `xargs`, `find -exec`, `echo … | sh`), a branch condition reads the directory the command acts on (`git -C`, `cd`), settings are guarded wherever they live, dry runs and `command -v` are two-way, the newest decision by time counts and a revoke holds on every branch, a once-grant is crossed once under a lock, a broken ledger is named, and `doors authorize` refuses an agent's shell.
- DeepSeek Harness (dsh) host (ROADMAP §4.9): `aloop install dsh` (alias `deepseek`; `--project P` or `--global`) renders a `dsh --profile aloop` profile in `$DSH_HOME` and installs the skills. Its native plugin (`plugin/hosts/dsh/aloop.mjs`) runs the same gates as Claude Code on dsh's own events: a deny before a tool runs, a block after an invalid artifact write, and a stop gate that steers a role back up to 3 times before the supervisor is told it is BLOCKED (or STUCK, released unverified). Roles are agent presets on two subagent tools, one per model route (`deepseek-v4-pro` for planning, `deepseek-flash` for work by default; `MODEL_PLANNING_DSH` / `MODEL_WORKER_DSH` override). A writer's `aloop bind` moves its working directory into its worktree, and `host-adapter` confines its commands, edits and `working_directory` there. Usage is recorded Claude-shaped per role, so budgets, the ledger and `run-summary` work unchanged. `make e2e-live HOST=dsh` drives it live and exits 2 with the fix when no `DEEPSEEK_API_KEY` is set. Reason: DeepSeek's own harness is the cheapest route to a strong model. The profile records the project it serves (or `--global`), so another project's `aloop-init` never re-renders it, and gates get the worker shell's environment (no API key). The mission layer's six roles run on dsh too: presets on their route group's subagent tool, `gate-mission-scope` on every write and command, and `gate-mission-artifact` at each role's start and stop; the mission skill installs as `aloop-mission`. A second review (3 lenses; 11 confirmed findings, each fixed with a test) closed: a working directory that vanished (dsh's tools move it back to the main tree, so the plugin judges `ensure()`'s directory and `host-adapter` refuses to run from a missing one), gate switches from the project's `.env` (dsh loads it; `SKIP_HOOKS`, `GATE_RUN_TESTS`, `REPO_DIR`, `AGENTIC_*` from it never reach a gate), an interrupted Stop that dropped the sprint-close notice, and the shared profile: its owner is the main tree, an older unrecorded profile is claimed only by the project that installed it, and a project it does not serve is told so by `install status`, `aloop-init` and the session itself. Tests: `dsh-plugin.test.mjs` (46 checks, emulated), `dsh-runtime.test.mjs` (50 checks on the real pinned dsh 0.2.1-rc.2 with a scripted model; the CI hosts job installs it) and `dsh-install.test.py` (10).
- dsh follow-ups (ROADMAP §4.9). One `aloop install dsh` serves every project: the plugin routes each role's requests through dsh's `agent/request` to its own project's model (`aloop models resolve <role>`), so gate-dispatch no longer refuses another project's roles until a re-install, and a role whose model cannot be resolved is refused (fail closed). Roles are no longer offered `send_message`, so dsh adds no "send your result" guidance and no call is spent on a deny. dsh compaction is recorded as `context.compacted` and the handoff comes back after it; the capability matrix marks dsh compaction and live messaging as advertised, proven on the real runtime. Two mission roles (one per subagent tool) run through both of their gates on the real runtime. `run-summary --host dsh` reports a dollar cost: each logged call is priced from a dated, sourced DeepSeek table (`plugin/bin/_prices.py`; cache hit, cache miss, output, peak or off-peak), with `cost_by_model` in `summary.json`; an unpriced model id keeps its tokens and leaves the cost unmeasured, never guessed.
Expand Down
4 changes: 2 additions & 2 deletions docs/ROADMAP.md

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions plugin/bin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ ctx-symbols) → WARN + grep fallback; never a silent pass.
| gate-structural-integrity | SubagentStop · structural-reviewer | appended output.md block; orphan/parallel/duplicate via ctx-symbols | no/stale output.md block / foundation-poisoning finding |
| gate-final | SubagentStop · final-gate | full matrix; zero suppressions; all plan-ready ticked | matrix red / suppression / unticked |
| gate-standards-cited | SubagentStop · standards | standards.md citations resolve | dangling citation |
| gate-stage2-complete | supervisor self-check + SubagentStop · planner | the sprint is in `docs/agents/sprintN/`, never the run-data dir; every story Stage-2 valid; no TBW; sprint self-contained (no later-sprint story IDs, no work deferred to a later sprint); verdicts logged like every gate, and the stuck release covers the planner (3 identical blocks → stop released unverified, marker `state/stuck/plan-sprintN.<agent>`) | incomplete or spilling planning |
| gate-stage2-complete | supervisor self-check + SubagentStop · planner | the sprint is in `docs/agents/sprintN/`, never the run-data dir; every story Stage-2 valid; no TBW; sprint self-contained (no later-sprint story IDs, no work deferred to a later sprint); verdicts logged like every gate, and the stuck release covers the planner (3 identical blocks → stop released unverified, marker `state/stuck/plan-sprintN.<agent>`); a pass on the planner's stop records the plan it passed (`ids gated`, M0.9) | incomplete or spilling planning |
| gate-plan-shape | standalone (not hook-wired) | each plan checkbox carries `path::fn` | malformed plan |
| gate-ledger-format | standalone (not hook-wired) | execution.log line shape | malformed ledger |

Expand Down Expand Up @@ -67,8 +67,8 @@ ctx-symbols) → WARN + grep fallback; never a silent pass.
| `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`; `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. |
| `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`. `gated SPRINT_DIR` (the planner's stop payload on stdin) records `plan.gated`, the plan's files' sha256 with their text kept: `gate-stage2-complete` runs it when the planner's stop passes, and under an autonomy grant it is the plan text `deps unplanned` reads. |
| `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), reading only the plan text the human's grant pins, as it was then (M0.9: the approved plan, or under autonomy the plan as the planner's gate passed it); 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). |
| `secrets` | secret scan (ROADMAP M10.2): `scan [--base REF]` reports `path:line: kind` for a private key or a fixed-prefix token (AWS, GitHub, GitLab, Slack, Stripe, Google, Anthropic, OpenAI), never the text; with `--base` only added lines. gate-green-verify scans the task's added lines, gate-final the whole project (exit 2). `aloop: allow-secret` on a line exempts a fixture. |
| `targets` | semantic execution map (ROADMAP §4.6): the planner's `<story>/targets.json` — change targets and, with `tasks`, the whole semantic plan. `render STORY_DIR [--check]` writes tasks.md / validate.md / plan.md as deterministic views over it (the gate refuses a hand-edited view) · `check STORY_DIR` (run by `gate-stage2-complete`) · `target Tn STORY_DIR` (file:line, or `planned` for the shadow workspace) · `shadow` · `scope Tn STORY_DIR` (the diff scope gate-green-verify derives when SCOPE_GLOBS is unset) · `context Cn --budget N` (just the target's definition) · `refs\|callers\|callees` via the LSP server in `AGENTIC_LSP_COMMAND` (`_lsp.py`: references + call hierarchy); no server or a missing capability → exit 4, never guessed. Backend: ctx-symbols, else a definition regex flagged weaker. |
Expand Down
Loading
Loading
0