8000 ROADMAP M1: worktree markers move to .aladdin/ with a .agentic/ read fallback by adeelahmad · Pull Request #23 · 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
5 changes: 3 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,9 @@ tag in lockstep. Tag releases as `vMAJOR.MINOR.PATCH`.
git-ignored. Every `AGENTIC_<KEY>` env var and settings key accepts `ALADDIN_<KEY>`
(`AGENTIC_` wins when both are set). The gates and `budget` find the task contract in
either dir, so a supervisor that writes the old literal path is still understood. The
worktree-local markers (`.agentic/task.env`, `budget-exceeded`, `scaffold-symbols`) keep
their name for now. Test: `scripts/test/layout.test.py`; every suite runs on the new layout.
worktree-local markers (`task.env` copy, `budget-exceeded`, `scaffold-symbols`) move with
it: written to the worker worktree's `.aladdin/` (guarded), read from `.agentic/` too.
Test: `scripts/test/layout.test.py`; every suite runs on the new layout.
- **Workers append their `output.md` block before `selfcheck`** (RED/SCAFFOLD/GREEN personas,
agents and pipeline sources). Reason: the soft-limit line said "run selfcheck, append your
report", so workers self-checked first and were blocked for a missing report block: 4 of
Expand Down
5 changes: 3 additions & 2 deletions docs/OVERVIEW.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,9 @@ The model can ignore a skill; it cannot ignore an exit code.
- `refs` / `callers` / `callees` need a configured LSP server; without one they say so (exit 4).
- Hosts do not report the model's context window; `AGENTIC_CONTEXT_WINDOW` is the human's number.
- `AGENTIC_*` settings keys stay canonical; `ALADDIN_*` is an alias (`AGENTIC_` wins when both
are set). Worktree-local markers (`.agentic/task.env`, `budget-exceeded`, `scaffold-symbols`)
keep their name for now (ROADMAP M1).
are set). Worktree-local markers (`task.env`, `budget-exceeded`, `scaffold-symbols`) live in
the worker worktree's `.aladdin/` too, and are read from `.agentic/` where a host or an
older sprint still writes them there.
- Measured runs: TheNoteBook plus the `make e2e-live` runs in ROADMAP §0; no live-session test in CI.

## The docs, and which ones are current
Expand Down
2 changes: 1 addition & 1 deletion docs/ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ spec; this table is the plan. Versions are targets, not promises.
| **M0.6 — Gates fail closed** | v0.11.x | A gate that crashes (exit 1, e.g. an unbound variable) is a non-blocking hook error on Claude Code, so the action goes through. Make every gate trap unexpected errors into exit 2 with the reason, and add a conformance case per gate. Reason: the smoke run showed one real instance | done: `_failclosed.sh` on all 10 sub-agent gates + supervisor-scope inside a sprint; `scripts/test/fail-closed.test.py` (5 checks). Left fail-open on purpose: `gate-commit-author` and `gate-validate-artifact` outside a sprint, which see every session on the machine |
| **M0.7 — Supervisor-owned task contract** | v0.11.x | The contract lives in the main tree, `.agentic/state/tasks/<TASK_ID>.env`, and the dispatch prompt's first line names the task; gates and the time box find it from the host's transcript, a worktree `task.env` that disagrees blocks, and sub-agents may not Write/Edit `.agentic/state/` or `task.env`. Settled by a live probe: Claude Code's `isolation: "worktree"` makes `.claude/worktrees/agent-<id>` at dispatch, so a pre-written in-worktree contract was impossible there. Bash writes are guarded too (a command naming the contract with a write verb is blocked; a heuristic, so an obfuscated write still gets through). On hosts with no sub-agent transcript the worktree's `TASK_ID` (or its `.agentic/worktrees/<id>` path) picks the contract | done: `make conformance` (58 today) + `scripts/test/{budget-worktree,supervisor-scope}.test.py` |
| **M0.5 — Rename to aloop** | v0.11.0 | Plugin, marketplace, skill, CLI (`aloop`, `aloop init`), OpenCode plugin file and docs renamed; `agentic`, `agentic init`, `agentic-init` and `agentic bind` kept as aliases; OpenCode/Codex installs clean up pre-rename files. Data layout and settings keys unchanged (moved to M1). GitHub repo rename is the owner's call | done: `scripts/test/rename-compat.test.py` |
| **M1 — Trustworthy state** | v0.12 | Session 1: conformance fixtures around current behaviour, canonical IDs and the reference remap (§4.5); then move the data layout to the new name (`.agentic/` → `.aloop/`, `AGENTIC_*` → `ALOOP_*`) with a read-old fallback, proven by those fixtures | done: §6 fixtures (`make conformance`, 58 checks incl. every false positive the live runs found) and canonical IDs + the reference remap (§4.5, `ids freeze`, `scripts/test/ids-integrity.test.py`); **layout move shipped (Unreleased)**: run data lives in `.aladdin/` (chosen over `.aloop/`, 2026-10-18, because `config.yaml` already lives there and the product name is Aladdin), guarded by `.aladdin/.gitignore`; a project with `.agentic/` keeps using it until `aloop layout migrate`; `ALADDIN_<KEY>` is an alias of every `AGENTIC_<KEY>` env/setting (`AGENTIC_` wins when both are set); the gates find the task contract in either dir. Test: `scripts/test/layout.test.py`; every suite runs on the new layout. Left: the worktree-local markers (`.agentic/task.env`, `budget-exceeded`, `scaffold-symbols`) keep their name (reason: hosts and older sprints write them; rename with a read-old window in a later PR) |
| **M1 — Trustworthy state** | v0.12 | Session 1: conformance fixtures around current behaviour, canonical IDs and the reference remap (§4.5); then move the data layout to the new name (`.agentic/` → `.aloop/`, `AGENTIC_*` → `ALOOP_*`) with a read-old fallback, proven by those fixtures | done: §6 fixtures (`make conformance`, 58 checks incl. every false positive the live runs found) and canonical IDs + the reference remap (§4.5, `ids freeze`, `scripts/test/ids-integrity.test.py`); **layout move shipped (Unreleased)**: run data lives in `.aladdin/` (chosen over `.aloop/`, 2026-10-18, because `config.yaml` already lives there and the product name is Aladdin), guarded by `.aladdin/.gitignore`; a project with `.agentic/` keeps using it until `aloop layout migrate`; `ALADDIN_<KEY>` is an alias of every `AGENTIC_<KEY>` env/setting (`AGENTIC_` wins when both are set); the gates find the task contract in either dir. Test: `scripts/test/layout.test.py`; every suite runs on the new layout. The worktree-local markers (`task.env`, `budget-exceeded`, `scaffold-symbols`) moved too: written to the worktree's `.aladdin/` (guarded), read from `.agentic/` as well (hosts and older sprints still write there). The layout move is complete |
| **M2 — Budgets that plan** | v0.13 | Sessions 2 and 4: budget semantics (§3.1–3.5), sprint envelope and admission control (§4.2), breach classes, failure envelope, grace rule, lesson candidates (§4.3) | done: §3.1–3.5 budget semantics, §4.2 sprint envelope + admission control (`budget admit`, `sweep`), §4.3 breach classes, failure envelope, grace rule and lesson candidates (tests: `budget-*.test.py`, `orchestrator-sweep.test.py`, `lesson-candidates.test.py`) |
| **M3 — Clean boundaries** | v0.14 | Session 3: canonical handoff, rotation and the resume rule (§4.4) | done: canonical `handoff.json` + NEXT.md view, rotation/compaction recorded from SessionStart, the resume rule (`stats resume-check`; tests: `context-boundary.test.py`, `handoff-resume.test.py`) |
| **M4 — Intent in, autonomy optional** | v0.15 | Session 5: Intent Contract, clarification policy, autonomous mode (§4.1) | done: Intent Contract (`bin/intent`), clarification policy, autonomous mode (tests: `intent-contract.test.py`); `aladdin "<intent>"` records it (§4.11) |
Expand Down
2 changes: 1 addition & 1 deletion docs/TRACKING.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,6 @@ Status of every requirement in [ROADMAP.md](ROADMAP.md), in the four states §8
| 4.10 | Context-window share (peak / `AGENTIC_CONTEXT_WINDOW`) | IMPLEMENTED_TESTED | `scripts/test/metrics.test.py` |
| 4.11 | Entry points: `aladdin` CLI (intent, run, status, resume, report, semantic queries) and `/aladdin` | IMPLEMENTED_TESTED | `scripts/test/aladdin-cli.test.py` |
| 4.11 | Compatibility aliases (`agentic`, `/aloop`, `.agentic/` alongside `.aladdin/`) | IMPLEMENTED_TESTED | `scripts/test/rename-compat.test.py` |
| M1 | Run data in `.aladdin/` (guarded by its `.gitignore`), `.agentic/` read as legacy, `aloop layout show\|check\|migrate`, `ALADDIN_*` aliases, contract found in either dir | IMPLEMENTED_TESTED | `scripts/test/layout.test.py` |
| M1 | Run data in `.aladdin/` (guarded by its `.gitignore`), `.agentic/` read as legacy, `aloop layout show\|check\|migrate`, `ALADDIN_*` aliases, contract and worktree markers found in either dir | IMPLEMENTED_TESTED | `scripts/test/layout.test.py` |
| 4.11 | README reordered product-first (intent → demo → TDD agents → loop → gates → install → hosts → evidence → audit → architecture → limitations) | IMPLEMENTED_UNTESTED | prose; its claims cite ROADMAP §0 runs and `make e2e-live` |
| 4.11 | Site (`docs/index.html`, GitHub Pages from `/docs`) with the lamp motif, RED explainer and audit tree; numbers sourced from §0 | IMPLEMENTED_TESTED | `scripts/test/site.test.py` |
2 changes: 1 addition & 1 deletion plugin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,5 +187,5 @@ MIT — see `LICENSE`.
(role-scoped) into each `init.md` `# Memory` section.
- The gates read their per-task contract (`TASK_ID`, `SCOPE_GLOBS`, `SCAFFOLD_SYMBOLS`,
`BASE_REF`, `BUDGET_*`) from `.agentic/state/tasks/<TASK_ID>.env` in the main tree,
written by the supervisor before dispatch (a worktree `.agentic/task.env` must match it). `.agentic/` and `.transcripts/` are git-ignored and never
written by the supervisor before dispatch (a worktree `.aladdin/task.env`, or `.agentic/` in a legacy project, must match it). `.agentic/` and `.transcripts/` are git-ignored and never
merged.
6 changes: 3 additions & 3 deletions plugin/bin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Gates read the supervisor's contract `<main tree>/.aladdin/state/tasks/<TASK_ID>
(`.agentic/...` in a legacy project; both dirs are searched)
(written before dispatch; the dispatch prompt's first line `TASK_ID: <id>` names it):
`TASK_ID, ATTEMPT, AGENT_ROLE, SCOPE_GLOBS, SCAFFOLD_SYMBOLS, BASE_REF, STORY_DIR,
BUDGET_*`. A worktree `.agentic/task.env` is a fallback and must match the contract when
BUDGET_*`. A worktree `.aladdin/task.env` (or `.agentic/`, read-old) is a fallback and must match the contract when
both exist. Parsed, never sourced. Real env overrides. Store paths are NOT part of the contract:
`_paths.sh` / `_paths.py` fix them (`<main tree>/.aladdin/{transcripts,budget,ledger,logs,state}`,
or `.agentic/` where a project still has one — `aloop layout show|check|migrate`;
Expand All @@ -29,7 +29,7 @@ ctx-symbols) → WARN + grep fallback; never a silent pass.
| 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-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 BD0 code / regression |
| gate-scaffold-verify | SubagentStop · scaffolder | worktree-isolated; appended output.md block; one def per symbol (`.agentic/scaffold-symbols`); panic+TODO; no marked shim; no clobber | no/stale output.md block / shared-tree run / impl body / dup / shim remains |
| 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 |
| 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 |
Expand Down Expand Up @@ -60,7 +60,7 @@ ctx-symbols) → WARN + grep fallback; never a silent pass.
| `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` (+ `.agentic/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) |
| `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). 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`. |
| `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`. |
Expand Down
10 changes: 5 additions & 5 deletions plugin/bin/_gatelib.sh
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ _gate_enter_bound_worktree
# `isolation: "worktree"` creates the worktree (.claude/worktrees/agent-<id>) at dispatch, so
# no one can pre-write a task.env inside it. The host records the dispatch prompt in the
# sub-agent transcript (SubagentStop's agent_transcript_path), so the task id comes from the
# host, not from the worker. A worktree .agentic/task.env (Codex/OpenCode, older sprints) is
# host, not from the worker. A worktree task.env copy (.aladdin/ or .agentic/; Codex/OpenCode, older sprints) is
# only a hint: when the contract exists, any key the copy sets must match it, or the gate
# blocks; with no contract the copy is the contract, as before.
#
Expand Down Expand Up @@ -176,7 +176,7 @@ _task_env_get() {
}
load_task_env() {
local te contract="" tid k v cv
te="$(repo_dir)/.agentic/task.env"
te="$(agentic_marker "$(repo_dir)" task.env)" || true
# Which task: the host's dispatch prompt, else the task worktree's own name
# (.agentic/worktrees/<TASK_ID>, made by `aloop task-worktree add`), else the copy's hint.
tid="$(_host_task_id)"
Expand All @@ -190,12 +190,12 @@ load_task_env() {
# contract; bin/budget refreshes the copy on the worker's next tool call), not tampering:
# a worker's edit makes the copy newer. Only a copy at least as new is compared.
if [ -n "$contract" ] && [ -f "$te" ] && [ "$te" -ot "$contract" ]; then
note "the worktree's .agentic/task.env is older than the contract $contract; using the contract"
note "the worktree's task.env copy ($te) is older than the contract $contract; using the contract"
elif [ -n "$contract" ]; then
while IFS=$'\t' read -r k v; do
cv="$(_task_env_get "$contract" "$k")" || cv=""
if [ "$cv" != "$v" ]; then
fail "the worktree's .agentic/task.env disagrees with the supervisor's contract on $k ('$v' vs '$cv' in $contract). The contract is the supervisor's: do not edit it."
fail "the worktree's task.env copy ($te) disagrees with the supervisor's contract on $k ('$v' vs '$cv' in $contract). The contract is the supervisor's: do not edit it."
fi
done < <(_task_env_kv "$te")
fi
Expand Down Expand Up @@ -237,7 +237,7 @@ _gate_bind_role
# the supervisor never merges it, and the task goes to a mid-sprint re-plan. (A worker
# forging the marker gains nothing: the only outcome is its task being re-planned.)
budget_release() {
local m; m="$(repo_dir)/.agentic/budget-exceeded"
local m; m="$(agentic_marker "$(repo_dir)" budget-exceeded)" || return 0
[ -f "$m" ] || return 0
case "${AGENT_ROLE:-}" in red-worker|scaffolder|green-worker) ;; *) return 0 ;; esac
grep -q "role=${AGENT_ROLE} attempt=${ATTEMPT:-}" "$m" 2>/dev/null || return 0
Expand Down
Loading
Loading
0