From d6ae5e5c4268d311278694d8219ec6d4fa5bcd49 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sat, 11 Jul 2026 02:04:36 +0000 Subject: [PATCH] Make task path overlap advisory. Replace deadlocking path ownership with visible activity and serialize only the final landing window, preserving worktree isolation without circular waits. Defense: wiki/process/agent-scale.md now defines task paths as advisory collision information and requires a short landing mutex; wiki/process/meta.md keeps the legacy metadata field without granting ownership. 👾 Generated with [Letta Code](https://letta.com) Co-Authored-By: Letta Code --- AGENT.md | 15 ++++++++------- prompts/README.md | 10 +++++----- prompts/implement-gap.md | 9 +++++---- tools/check.sh | 4 ++-- tools/claim.sh | 69 +++++++++++++++++++++++++++++++++------------------------------------ tools/project-status.py | 15 ++++++++++----- tools/task.sh | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------- tools/test_project_ops.py | 87 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----- tools/worktree-done.sh | 4 ++-- tools/worktree-new.sh | 12 ++++++------ wiki/engineering/env.md | 6 ++++-- wiki/log/2026-07-10-advisory-task-activity.md | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++ wiki/log/DEVLOG.md | 5 +++++ wiki/process/ROADMAP.md | 19 +++++++++++-------- wiki/process/agent-scale.md | 113 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------------------------- wiki/process/meta.md | 11 +++++++---- wiki/process/workflows.md | 8 ++++---- wiki/log/decisions/2026-07-10.md | 12 ++++++++++++ 18 file(s) changed, 390 insertion(s)(+), 146 deletion(s)(-) diff --git a/AGENT.md b/AGENT.md --- a/AGENT.md +++ b/AGENT.md @@ -57,17 +57,18 @@ - **Always use a worktree for file changes.** Never edit the shared primary checkout. A read-only design conversation creates no worktree. - Prefer `tools/worktree-new.sh --class - --key ...` (seeds cargo target and takes a local claim). Finish with - `tools/worktree-done.sh ` (releases claim, prunes `target/`, removes + --key ...` (seeds cargo target and records advisory activity). Finish with + `tools/worktree-done.sh ` (clears activity, prunes `target/`, removes worktree). The canonical root is `.Codex/worktrees/`; operators may override it with `MISALIGNED_WORKTREE_ROOT` without changing the repository contract. - For a spec carrying work-order metadata, prefer `tools/task.sh start wiki/path.md`; `tools/task.sh status|check|finish|abandon` composes the same helpers without auto-committing or destructive Git recovery. Run - `tools/project-status.py` for the live dispatch/worktree/claim view and + `tools/project-status.py` for the live dispatch/worktree/activity view and `tools/doctor.sh --offline` for local setup diagnosis. -- **Claim before heavy work.** Exclusive keys must not overlap an active - claim on this machine (`tools/claim.sh list`). See +- **Advertise before heavy work.** Inspect `tools/claim.sh list` and record + likely edit paths. Overlap is a warning, not ownership: proceed in an + isolated worktree and reconcile against current main at landing. See [wiki/process/agent-scale.md](wiki/process/agent-scale.md). - A shell working directory does not guarantee that every patch tool resolves relative paths there. Verify the patch base before the first edit, use an @@ -89,8 +90,8 @@ [wiki/process/workflows.md](wiki/process/workflows.md)): - Mid-loop: narrowest `./tools/check.sh --docs|--lib|--frontend` (or auto). - Land: one `./tools/check.sh --land` (or auto/`--full`) under the rust lock. - - At most one Rust gate runs on the machine at a time (shared lock). Claims - block overlapping exclusive keys even when gates would not. + - At most one Rust gate runs on the machine at a time (shared lock). One + `task.sh finish` landing runs at a time. Path activity never blocks work. - Docs/spec/process/log/reference-art work stays on the docs path. - Site and shell changes run their targeted build, syntax, or smoke checks. - Rust-impacting landings still need a relevant observed run. diff --git a/prompts/README.md b/prompts/README.md --- a/prompts/README.md +++ b/prompts/README.md @@ -21,11 +21,11 @@ `Type: law`, owning `Type: spec`, dependencies, and nearby `Type: knowledge` pages it orders. Do not skip this on later runs — the corpus moves daily. -2. **Worktree + claim, always.** First action: create a claimed worktree +2. **Worktree + activity, always.** First action: create an isolated worktree (`tools/worktree-new.sh --class - --key ...`) or equivalent worktree + `tools/claim.sh claim`. - Never edit the primary checkout. If claim fails on exclusive keys, - stop — another agent owns that surface (`tools/claim.sh list`). + --key ...`) or equivalent worktree + `tools/claim.sh activity`. + Never edit the primary checkout. An overlap warning means another task is + nearby, not that it owns the file; continue and reconcile at landing. 3. **The one rule.** Functional changes ship with their owning binding wiki amendment in the same commit; every behavior-changing commit carries a `Defense:` paragraph naming the clause that justifies it. @@ -35,7 +35,7 @@ `./tools/check.sh --land` (or auto/`--full`). At most one Rust gate runs on the machine at a time (shared lock). Then land by rebasing onto `origin/main` and pushing to `main` directly (no PRs). Finish - with `tools/worktree-done.sh ` (releases claim, prunes target). + with `tools/worktree-done.sh ` (clears activity, prunes target). 5. **Heartbeat on long runs.** `tools/heartbeat.sh start ` then `phase` / `end` so empty stdout is not the only progress signal. 6. **Ledgers merge by union.** Conflicts in `wiki/log/DEVLOG.md`, diff --git a/prompts/implement-gap.md b/prompts/implement-gap.md --- a/prompts/implement-gap.md +++ b/prompts/implement-gap.md @@ -12,7 +12,7 @@ `wiki/process/specs.md` (the status table), and `wiki/process/ROADMAP.md` (the dispatch board — it may already name the highest-value gap and its worktree name). Obey the shared contract in `prompts/README.md`: -worktree+claim first (`tools/worktree-new.sh` or claim.sh), one rule, +worktree+activity first (`tools/worktree-new.sh` or `claim.sh activity`), one rule, check.sh gate (`--land` before merge), heartbeat on long runs, direct merge to main, union merges on ledgers. @@ -43,7 +43,7 @@ (`#[cfg(test)]` beside the module or in `src/sim.rs`'s tests). 5. **Update the spec's `Status:`/`Status note:`** in the same commit (READY -> IN PROGRESS -> IMPLEMENTED per `wiki/process/meta.md`). If - your work changes behavior beyond what the spec says, that needs a + your work changes behavior beyond what the spec says, that needs an amendment to the owning law/spec page in the same commit — if you find yourself *designing* rather than implementing, stop and use `prompts/ask-the-human.md` instead. @@ -52,8 +52,9 @@ `./tools/check.sh --land`. Write `wiki/log/YYYY-MM-DD-topic.md`, run `tools/ledger_index.sh` (do not hand-edit DEVLOG.md or specs.md). Rebase onto `origin/main`, push to `main`, `tools/worktree-done.sh - `. Mark the ROADMAP item's result if you closed one. Do not - claim exclusive keys another agent already holds. + `. Mark the ROADMAP item's result if you closed one. Inspect + overlapping activity, but do not wait on path ownership: work in + your isolated tree and reconcile against current main when landing. ## Definition of done diff --git a/tools/check.sh b/tools/check.sh --- a/tools/check.sh +++ b/tools/check.sh @@ -11,8 +11,8 @@ # ./tools/check.sh --land # land policy: auto-classify (full when unclear) # # Concurrent agents: at most one Rust gate runs at a time on this machine -# (mkdir lock). Docs gates never take the lock. Semantic exclusivity is -# tools/claim.sh (agent-scale slice A), not this lock. +# (mkdir lock). Docs gates never take the lock. `tools/claim.sh` records +# advisory task surfaces; it is not a semantic mutex. # # An unclassifiable clean primary checkout stays conservative (full). Tangled # runs the same docs gates separately and classifies Rust impact at workflow diff --git a/tools/claim.sh b/tools/claim.sh --- a/tools/claim.sh +++ b/tools/claim.sh @@ -1,16 +1,17 @@ #!/usr/bin/env bash -# Local work-order claims — semantic exclusivity for concurrent agents. -# Binding behavior: wiki/process/agent-scale.md slice A. +# Local work-order activity — advisory overlap visibility for concurrent agents. +# Binding behavior: wiki/process/agent-scale.md slice A (amended 2026-07-10). # -# Claims live under .agents/claims/ (gitignored, machine-local). Any agent -# on this machine discovers active work via `tools/claim.sh list`. +# Records retain the historical `.agents/claims/` path and `claim` command so +# older worktrees keep interoperating. Path overlap warns but never blocks; +# worktrees isolate edits and the landing phase reconciles against current main. # # Usage: -# tools/claim.sh claim --class \ +# tools/claim.sh activity --class \ # [--key path]... [--force] # tools/claim.sh status [id] # tools/claim.sh list -# tools/claim.sh set-status +# tools/claim.sh set-status # tools/claim.sh release [--status done|abandoned] # tools/claim.sh check --key path [--key path]... # @@ -30,7 +31,7 @@ CLAIMS_DIR=${MISALIGNED_CLAIMS_DIR:-"$root/.agents/claims"} WORKTREE_ROOT=${MISALIGNED_WORKTREE_ROOT:-"$root/.Codex/worktrees"} case "$WORKTREE_ROOT" in /*) ;; *) WORKTREE_ROOT="$root/$WORKTREE_ROOT" ;; esac -ACTIVE_STATUSES="claimed checking landing" +ACTIVE_STATUSES="claimed blocked checking landing" usage() { sed -n '2,20p' "$0" | sed 's/^# \{0,1\}//' @@ -46,7 +47,7 @@ # Sanitize id to a single path segment. case "$id" in ""|*/*|*\\*|*..*) - echo "FAIL: claim id must be a single path segment (got '$id')" >&2 + echo "FAIL: activity id must be a single path segment (got '$id')" >&2 exit 2 ;; esac @@ -55,7 +56,7 @@ is_active() { case "$1" in - claimed|checking|landing) return 0 ;; + claimed|blocked|checking|landing) return 0 ;; *) return 1 ;; esac } @@ -149,7 +150,7 @@ if [ -d "$WORKTREE_ROOT/$c_id" ]; then continue fi - echo "claim: reaping stale claim '$c_id' (dead pid $c_pid, was $c_status)" >&2 + echo "activity: reaping stale record '$c_id' (dead pid $c_pid, was $c_status)" >&2 rm -f "$path" fi done @@ -163,12 +164,12 @@ [ -f "$path" ] || continue read_claim "$path" || continue any=1 - printf '%s class=%s status=%s pid=%s keys=%s\n' \ + printf '%s class=%s status=%s pid=%s paths=%s\n' \ "$c_id" "$c_class" "$c_status" "$c_pid" \ "$(IFS=,; echo "${c_keys[*]-}")" done if [ "$any" -eq 0 ]; then - echo "(no active claims in $CLAIMS_DIR)" + echo "(no active task activity in $CLAIMS_DIR)" fi } @@ -182,7 +183,7 @@ local path path=$(claim_path "$id") if ! read_claim "$path"; then - echo "FAIL: no claim '$id' ($path)" >&2 + echo "FAIL: no activity record '$id' ($path)" >&2 exit 1 fi printf 'id=%s\nclass=%s\nstatus=%s\npid=%s\nstarted=%s\nhost=%s\n' \ @@ -223,10 +224,11 @@ esac done if find_conflict "${keys[@]+"${keys[@]}"}"; then - echo "FAIL: keys conflict with active claim '$conflict_id' (keys: $conflict_keys)" >&2 - exit 1 + echo "WARN: paths overlap active task '$conflict_id' (paths: $conflict_keys)" >&2 + echo " overlap is advisory; finish in a worktree and reconcile at landing" >&2 + exit 0 fi - echo "check: ok (no active claim holds those keys)" + echo "check: ok (no active task advertises those paths)" } cmd_claim() { @@ -260,27 +262,22 @@ read_claim "$path" if is_active "$c_status"; then if [ "$force" -eq 1 ]; then - echo "claim: --force replacing existing claim '$id' (was $c_status pid $c_pid)" + echo "activity: --force replacing '$id' (was $c_status pid $c_pid)" elif pid_alive "$c_pid" && [ "$c_pid" = "$holder" ]; then : # same agent re-claim updates keys/status elif pid_alive "$c_pid"; then - echo "FAIL: claim '$id' already active (status=$c_status pid=$c_pid keys=$(IFS=,; echo "${c_keys[*]-}"))" >&2 - exit 1 + echo "WARN: replacing activity '$id' owned by pid $c_pid; task identity is shared" >&2 else - echo "claim: replacing claim '$id' from dead pid $c_pid" + echo "activity: replacing '$id' from dead pid $c_pid" fi fi fi - if [ "$force" -eq 0 ] && find_conflict "${keys[@]+"${keys[@]}"}"; then - # Allow if the only conflict is ourselves (re-claim same id). - if [ "$conflict_id" != "$id" ]; then - echo "FAIL: exclusive key conflict with claim '$conflict_id'" >&2 - echo " their keys: $conflict_keys" >&2 - echo " your keys: $(IFS=,; echo "${keys[*]-}")" >&2 - echo " list: tools/claim.sh list" >&2 - exit 1 - fi + if find_conflict "${keys[@]+"${keys[@]}"}" && [ "$conflict_id" != "$id" ]; then + echo "WARN: paths overlap active task '$conflict_id'" >&2 + echo " their paths: $conflict_keys" >&2 + echo " your paths: $(IFS=,; echo "${keys[*]-}")" >&2 + echo " both tasks may proceed in separate worktrees; reconcile at landing" >&2 fi c_id=$id @@ -293,7 +290,7 @@ c_host=$(hostname 2>/dev/null || echo unknown) c_keys=("${keys[@]+"${keys[@]}"}") write_claim "$path" - echo "claim: acquired '$id' class=$class status=claimed pid=$c_pid keys=$(IFS=,; echo "${c_keys[*]-}")" + echo "activity: recorded '$id' class=$class status=claimed pid=$c_pid paths=$(IFS=,; echo "${c_keys[*]-}")" echo " file $path" } @@ -301,9 +298,9 @@ local id="${1:-}" status="${2:-}" [ -n "$id" ] && [ -n "$status" ] || usage case "$status" in - claimed|checking|landing|done|abandoned) ;; + claimed|blocked|checking|landing|done|abandoned) ;; *) - echo "FAIL: status must be claimed|checking|landing|done|abandoned" >&2 + echo "FAIL: status must be claimed|blocked|checking|landing|done|abandoned" >&2 exit 2 ;; esac @@ -311,12 +308,12 @@ local path path=$(claim_path "$id") if ! read_claim "$path"; then - echo "FAIL: no claim '$id'" >&2 + echo "FAIL: no activity record '$id'" >&2 exit 1 fi if [ "$status" = "done" ] || [ "$status" = "abandoned" ]; then rm -f "$path" - echo "claim: '$id' -> $status (cleared)" + echo "activity: '$id' -> $status (cleared)" return 0 fi c_status=$status @@ -325,7 +322,7 @@ c_pid=$MISALIGNED_CLAIM_PID fi write_claim "$path" - echo "claim: '$id' -> $status" + echo "activity: '$id' -> $status" } cmd_release() { @@ -345,7 +342,7 @@ local cmd="${1:-}" shift || true case "$cmd" in - claim) cmd_claim "$@" ;; + activity|claim) cmd_claim "$@" ;; list) cmd_list "$@" ;; status) cmd_status "$@" ;; set-status) cmd_set_status "$@" ;; diff --git a/tools/project-status.py b/tools/project-status.py --- a/tools/project-status.py +++ b/tools/project-status.py @@ -157,7 +157,9 @@ "primary_root": str(primary), "recommended_next": next_lane, "work_orders": lanes, - "claims": key_value_files(primary / ".agents/claims", ".claim"), + # Storage keeps the historical path so older worktrees remain visible; + # these records are advisory activity, not exclusive ownership. + "activities": key_value_files(primary / ".agents/claims", ".claim"), "runs": run_files(primary / ".agents/runs"), "worktrees": worktree_rows(primary), "issues": issues(primary, offline), @@ -183,10 +185,13 @@ lines.append( f" {row['priority']:>3} {row['task']} [{row['status']}/{row['class']}]{blocker}" ) - lines.extend(["", f"Claims ({len(payload['claims'])}):"]) - if payload["claims"]: - for claim in payload["claims"]: - lines.append(f" {claim.get('id', '?')} {claim.get('status', '?')} keys={','.join(claim.get('keys', []))}") + lines.extend(["", f"Activities ({len(payload['activities'])}):"]) + if payload["activities"]: + for activity in payload["activities"]: + lines.append( + f" {activity.get('id', '?')} {activity.get('status', '?')} " + f"paths={','.join(activity.get('keys', []))}" + ) else: lines.append(" none") lines.extend(["", f"Runs ({len(payload['runs'])}):"]) diff --git a/tools/task.sh b/tools/task.sh --- a/tools/task.sh +++ b/tools/task.sh @@ -10,6 +10,65 @@ fi worktree_root=${MISALIGNED_WORKTREE_ROOT:-.Codex/worktrees} case "$worktree_root" in /*) ;; *) worktree_root="$primary/$worktree_root" ;; esac +LANDING_LOCK_DIR=${MISALIGNED_LANDING_LOCK:-/tmp/misaligned-landing.lock} +landing_lock_held=0 + +release_landing_lock() { + if [ "$landing_lock_held" -eq 1 ]; then + local holder + holder=$(cat "$LANDING_LOCK_DIR/pid" 2>/dev/null || true) + if [ "$holder" = "$$" ]; then + rm -rf "$LANDING_LOCK_DIR" + fi + landing_lock_held=0 + fi +} + +clear_stale_landing_lock() { + local stale="$LANDING_LOCK_DIR.stale.$$" + if mv "$LANDING_LOCK_DIR" "$stale" 2>/dev/null; then + rm -rf "$stale" + return 0 + fi + return 1 +} + +acquire_landing_lock() { + local waited=0 holder + while ! mkdir "$LANDING_LOCK_DIR" 2>/dev/null; do + holder=$(cat "$LANDING_LOCK_DIR/pid" 2>/dev/null || true) + if [ -z "$holder" ]; then + # mkdir and pid write are separate operations. Give a live acquirer one + # second to publish its pid before treating a pidless directory as stale. + sleep 1 + holder=$(cat "$LANDING_LOCK_DIR/pid" 2>/dev/null || true) + fi + if [ -n "$holder" ] && ! kill -0 "$holder" 2>/dev/null; then + echo "task: clearing stale landing lock (dead pid $holder)" + clear_stale_landing_lock || true + continue + fi + if [ -z "$holder" ]; then + echo "task: clearing stale landing lock (no holder pid)" + clear_stale_landing_lock || true + continue + fi + if [ "${MISALIGNED_LANDING_WAIT:-1}" = "0" ]; then + echo "FAIL: another task is landing (pid ${holder:-unknown})" >&2 + return 1 + fi + if [ "$waited" -eq 0 ]; then + echo "task: waiting for landing lock (pid ${holder:-unknown})" + fi + waited=$((waited + 1)) + sleep 1 + done + printf '%s\n' "$$" > "$LANDING_LOCK_DIR/pid" + landing_lock_held=1 + trap release_landing_lock EXIT + trap 'release_landing_lock; exit 130' INT TERM HUP + echo "task: acquired landing lock (pid $$)" +} usage() { cat <<'EOF' @@ -91,6 +150,8 @@ printf '%s\n' "$dirty" >&2 exit 1 } + bash "$wt/tools/heartbeat.sh" phase "$task" landing-wait + acquire_landing_lock primary_dirty=$(git -C "$primary" status --porcelain) [ -z "$primary_dirty" ] || { echo "FAIL: primary checkout is dirty; finish will not merge into it" >&2 @@ -112,6 +173,7 @@ git -C "$primary" merge --ff-only "worktree-$task" git -C "$primary" push origin main bash "$wt/tools/heartbeat.sh" end "$task" --status ok + release_landing_lock bash "$primary/tools/worktree-done.sh" "$task" } @@ -124,13 +186,15 @@ bash "$primary/tools/worktree-done.sh" "$task" --status abandoned } -command=${1:-}; shift || true -case "$command" in - start) cmd_start "$@" ;; - status) python3 "$script_root/tools/project-status.py" "$@" ;; - check) cmd_check "$@" ;; - finish) cmd_finish "$@" ;; - abandon) cmd_abandon "$@" ;; - -h|--help|help) usage ;; - *) usage >&2; exit 2 ;; -esac +if [ "${BASH_SOURCE[0]}" = "$0" ]; then + command=${1:-}; shift || true + case "$command" in + start) cmd_start "$@" ;; + status) python3 "$script_root/tools/project-status.py" "$@" ;; + check) cmd_check "$@" ;; + finish) cmd_finish "$@" ;; + abandon) cmd_abandon "$@" ;; + -h|--help|help) usage ;; + *) usage >&2; exit 2 ;; + esac +fi diff --git a/tools/test_project_ops.py b/tools/test_project_ops.py --- a/tools/test_project_ops.py +++ b/tools/test_project_ops.py @@ -132,14 +132,14 @@ parsed = [scenario.parse(path) for path in sorted((repo / "scenarios").glob("*.agent"))] self.assertGreaterEqual(len(parsed), 3) - def test_claim_prefix_collision_and_task_missing_refusal(self) -> None: + def test_activity_overlap_warns_without_blocking_and_task_missing_refuses(self) -> None: repo = Path(__file__).resolve().parent.parent claims = self.root / "claims" env = os.environ.copy() env["MISALIGNED_CLAIMS_DIR"] = str(claims) env["MISALIGNED_CLAIM_PID"] = str(os.getpid()) first = subprocess.run( - ["bash", "tools/claim.sh", "claim", "first", "--class", "sim", "--key", "crates/misaligned-core"], + ["bash", "tools/claim.sh", "activity", "first", "--class", "sim", "--key", "crates/misaligned-core"], cwd=repo, env=env, stdout=subprocess.PIPE, @@ -157,8 +157,29 @@ text=True, check=False, ) - self.assertNotEqual(0, second.returncode) - self.assertIn("conflict", second.stdout.lower()) + self.assertEqual(0, second.returncode, second.stdout) + self.assertIn("overlap", second.stdout.lower()) + blocked = subprocess.run( + ["bash", "tools/claim.sh", "set-status", "second", "blocked"], + cwd=repo, + env=env, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + check=False, + ) + self.assertEqual(0, blocked.returncode, blocked.stdout) + check = subprocess.run( + ["bash", "tools/claim.sh", "check", "--key", "crates/misaligned-core/src/sim.rs"], + cwd=repo, + env=env, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + check=False, + ) + self.assertEqual(0, check.returncode, check.stdout) + self.assertIn("advisory", check.stdout.lower()) missing = subprocess.run( ["bash", "tools/task.sh", "finish", "definitely-missing"], cwd=repo, @@ -181,7 +202,7 @@ self.assertIn("worktree-new.sh", planned.stdout) self.assertIn("project-operations", planned.stdout) - def test_dead_pid_claim_survives_while_task_worktree_exists(self) -> None: + def test_dead_pid_activity_survives_while_task_worktree_exists(self) -> None: repo = Path(__file__).resolve().parent.parent claims = self.root / "claims" claims.mkdir() @@ -207,6 +228,62 @@ self.assertEqual(0, result.returncode, result.stdout) self.assertIn("project-operations", result.stdout) self.assertTrue((claims / "project-operations.claim").is_file()) + + def test_landing_lock_refuses_live_holder_and_reaps_dead_holder(self) -> None: + repo = Path(__file__).resolve().parent.parent + lock = self.root / "landing.lock" + lock.mkdir() + (lock / "pid").write_text(f"{os.getpid()}\n", encoding="utf-8") + env = os.environ.copy() + env["MISALIGNED_LANDING_LOCK"] = str(lock) + env["MISALIGNED_LANDING_WAIT"] = "0" + live = subprocess.run( + ["bash", "-c", "source tools/task.sh; acquire_landing_lock"], + cwd=repo, + env=env, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + check=False, + ) + self.assertNotEqual(0, live.returncode) + self.assertIn("another task is landing", live.stdout) + + (lock / "pid").write_text("999999\n", encoding="utf-8") + stale = subprocess.run( + [ + "bash", + "-c", + "source tools/task.sh; acquire_landing_lock; release_landing_lock", + ], + cwd=repo, + env=env, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + check=False, + ) + self.assertEqual(0, stale.returncode, stale.stdout) + self.assertIn("clearing stale landing lock", stale.stdout) + self.assertFalse(lock.exists()) + + lock.mkdir() + pidless = subprocess.run( + [ + "bash", + "-c", + "source tools/task.sh; acquire_landing_lock; release_landing_lock", + ], + cwd=repo, + env=env, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + check=False, + ) + self.assertEqual(0, pidless.returncode, pidless.stdout) + self.assertIn("no holder pid", pidless.stdout) + self.assertFalse(lock.exists()) if __name__ == "__main__": diff --git a/tools/worktree-done.sh b/tools/worktree-done.sh --- a/tools/worktree-done.sh +++ b/tools/worktree-done.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Finish a task worktree: release claim, remove worktree + branch, prune target. +# Finish a task worktree: clear activity, remove worktree + branch, prune target. # Binding: wiki/process/agent-scale.md slices A + D. # # Usage: @@ -88,7 +88,7 @@ if [ -x "$primary/tools/claim.sh" ] || [ -f "$primary/tools/claim.sh" ]; then bash "$primary/tools/claim.sh" release "$task" --status "$status" 2>/dev/null || \ - echo "worktree-done: no claim named '$task' (ok)" + echo "worktree-done: no activity named '$task' (ok)" fi echo "worktree-done: finished $task ($status)" diff --git a/tools/worktree-new.sh b/tools/worktree-new.sh --- a/tools/worktree-new.sh +++ b/tools/worktree-new.sh @@ -1,16 +1,16 @@ #!/usr/bin/env bash -# Create a seeded task worktree and optionally take a local claim. +# Create a seeded task worktree and optionally record local task activity. # Binding: wiki/process/agent-scale.md slices A + D. # # Usage: # tools/worktree-new.sh [--class ] \ -# [--key path]... [--no-seed] [--no-claim] +# [--key path]... [--no-seed] [--no-activity] # # Creates (default; override root with MISALIGNED_WORKTREE_ROOT): # .Codex/worktrees/ on branch worktree- from origin/main # Runs tools/seed-cargo-target.sh unless --no-seed. -# Claims with the given class/keys unless --no-claim (default class=process -# with no keys if class omitted — docs-parallel; prefer an explicit --class). +# Records with the given class/paths unless --no-activity. `--no-claim` +# remains a compatibility alias. Overlap is advisory; the worktree is isolation. set -euo pipefail script_root=$(cd "$(dirname "$0")/.." && pwd) @@ -45,7 +45,7 @@ --class) shift; class="${1:-}"; shift || true ;; --key) shift; keys+=("${1:-}"); shift || true ;; --no-seed) seed=0; shift ;; - --no-claim) do_claim=0; shift ;; + --no-activity|--no-claim) do_claim=0; shift ;; -h|--help) usage ;; -*) echo "unknown flag: $1" >&2 @@ -102,7 +102,7 @@ if [ -z "$class" ]; then class=process fi - claim_args=(claim "$task" --class "$class") + claim_args=(activity "$task" --class "$class") for k in "${keys[@]+"${keys[@]}"}"; do claim_args+=(--key "$k") done diff --git a/wiki/engineering/env.md b/wiki/engineering/env.md --- a/wiki/engineering/env.md +++ b/wiki/engineering/env.md @@ -53,8 +53,10 @@ | `MISALIGNED_ALLOW_EXTERNAL_TARGET` | `tools/check.sh` | `1` | Permit an external `CARGO_TARGET_DIR` (normally rejected: shared target dirs across worktrees can produce false-green tests). | | `MISALIGNED_RUST_GATE_WAIT` | `tools/check.sh` | `0` or `1` (default `1`) | When another agent holds the Rust gate lock, `1` queues; `0` fails immediately. | | `MISALIGNED_RUST_GATE_LOCK` | `tools/check.sh` | path | Override the Rust gate lock directory (default `/tmp/misaligned-rust-gate.lock`). | -| `MISALIGNED_CLAIMS_DIR` | `tools/claim.sh` | path | Override claim store (default `/.agents/claims`, gitignored). | -| `MISALIGNED_CLAIM_PID` | `tools/claim.sh` | pid | Holder pid written into a claim (default: parent of the claim tool). | +| `MISALIGNED_CLAIMS_DIR` | `tools/claim.sh` | path | Override advisory activity store (historical default `/.agents/claims`, gitignored). | +| `MISALIGNED_CLAIM_PID` | `tools/claim.sh` | pid | Holder pid written into an activity record (default: parent of the compatibility tool). | +| `MISALIGNED_LANDING_WAIT` | `tools/task.sh finish` | `0` or `1` (default `1`) | When another task owns the final landing lock, `1` queues; `0` fails immediately. | +| `MISALIGNED_LANDING_LOCK` | `tools/task.sh finish` | path | Override the final rebase/check/merge/push lock directory (default `/tmp/misaligned-landing.lock`). | | `MISALIGNED_RUNS_DIR` | `tools/heartbeat.sh` | path | Override run heartbeat dir (default `/.agents/runs`, gitignored). | | `MISALIGNED_WORKTREE_ROOT` | `tools/worktree-new.sh`, `tools/worktree-done.sh` | absolute path or repository-relative path | Override the canonical task-worktree root (default `/.Codex/worktrees`). Both helpers must receive the same override. | | `MISALIGNED_LEDGER_MODE` | `tools/ledger_index.sh` | `write` or `check` | Internal: write regenerated indexes or fail if stale (set by the script, not hand-used). | diff --git a/wiki/log/2026-07-10-advisory-task-activity.md b/wiki/log/2026-07-10-advisory-task-activity.md new file mode 100644 --- /dev/null +++ b/wiki/log/2026-07-10-advisory-task-activity.md @@ -0,0 +1,53 @@ +# 2026-07-10 — Advisory task activity, not path ownership + +``` +Type: log +``` + +## Intent + +Break a live coordination deadlock and remove the mechanism that created it. +The action-contract task held an exclusive `crates/misaligned-bevy/src/main.rs` +claim while reporting that it was blocked by eyes-camera-exposure; that +prerequisite needed the same file. Machine-consumption-speed and another +conversation then waited behind action-contract. Every branch was isolated in +a worktree, but the path lock turned a normal rebase order into a wait cycle. + +## Changed + +- Cleared the blocking machine-local action-contract record immediately so + eyes-camera-exposure could proceed. +- Converted `tools/claim.sh activity` into a compatibility-named advisory activity + registry. Overlapping paths print both tasks and proceed with exit 0. +- Added a visible `blocked` activity state. A blocked record remains useful to + the dashboard but never owns a path. +- Reinterpreted work-order `Exclusive keys` as the stable legacy field name + for likely edit surfaces. The corpus schema stays intact; its values now + predict reconciliation cost rather than permission. +- Changed the project dashboard's human/JSON surface from `claims` to + `activities`. +- Added a portable machine-local landing lock to `tools/task.sh finish` around + final fetch/rebase/check/fast-forward/push. It queues by default, supports a + fail-fast environment switch, and reaps a dead holder. +- Updated AGENT, dispatch prompts, ROADMAP, workflow, environment, metadata, + and agent-scale current law. + +## Why this boundary + +Worktrees already isolate implementation. Git already exposes textual +overlap, and the corpus plus tests are the semantic reconciliation gate. Hard +path locks add a second ownership system with worse liveness than Git. The +only real scarce phases are machine-wide Rust verification and mutation of +main, so those remain serialized. + +The explicit ROADMAP rule of one sim+save-heavy work order at a time remains; +it is a dispatch decision about semantic blast radius, not a file lock. + +## Evidence + +- Project-operation fixtures prove overlapping activity succeeds with an + advisory warning and accepts the `blocked` state. +- Landing-lock fixtures prove a live holder is refused and a dead holder is + reaped. +- Shell syntax, corpus, wiki, work-order, ledger, environment, and project + operation gates run through the normal docs check. diff --git a/wiki/log/DEVLOG.md b/wiki/log/DEVLOG.md --- a/wiki/log/DEVLOG.md +++ b/wiki/log/DEVLOG.md @@ -251,6 +251,11 @@ - Intent: Remove the repository-specific prohibition on crediting AI contributors in commit messages. Cameron decided that accurate AI attribution is acceptable; the existing plain-message and explicit-staging rules remain. - Log: [wiki/log/2026-07-10-allow-ai-attribution.md](2026-07-10-allow-ai-attribution.md) +## 2026-07-10 - Advisory task activity, not path ownership + +- Intent: Break a live coordination deadlock and remove the mechanism that created it. The action-contract task held an exclusive `crates/misaligned-bevy/src/main.rs` claim while reporting that it was blocked by eyes-camera-exposure; that prerequisite needed the same file. Machine-consu... +- Log: [wiki/log/2026-07-10-advisory-task-activity.md](2026-07-10-advisory-task-activity.md) + ## 2026-07-10 - Action vocabulary survey - Intent: Survey every action and command already exposed by the game, then create one binding spec that says which words are canonical, what they mean, and whether they are live, interface-only, aliases, stubs, or retired. diff --git a/wiki/process/ROADMAP.md b/wiki/process/ROADMAP.md --- a/wiki/process/ROADMAP.md +++ b/wiki/process/ROADMAP.md @@ -53,7 +53,8 @@ ## How to dispatch Prefer `tools/task.sh start `; it resolves the owning spec's task, -claim class, and exclusive keys into a seeded claimed worktree. The detailed +work class and likely edit paths into a seeded worktree plus advisory activity. +Overlapping paths predict rebase work but never block dispatch. The detailed **Dispatch** lines below preserve implementation context for older entries, but their status does not override the generated live index. Every agent must, per [AGENT.md](../../AGENT.md), run the proportional check, land by rebasing @@ -72,8 +73,8 @@ contention. - 🟩 **isolated** — a frontend package (`misaligned-bevy` / `misaligned-terminal` / `misaligned-assets`), a test file, a leaf module, - or docs. Safe to run alongside anything that does not share exclusive - claim keys. + or docs. Safe to run alongside other work; shared-file edits reconcile at + landing like any other branch. **Parallel-safe set to launch right now (no mutual collision):** any ready 🟩 item, plus at most one 🟥 item. #30 narration is done; new player-facing @@ -755,10 +756,12 @@ agent**. Do not start the crate workspace until compile cost or package edges clearly block parallel work — coordination slices land first. -### P1. Dispatch claims (semantic exclusivity) 🟩 process — DONE 2026-07-09 +### P1. Dispatch activity (advisory overlap) 🟩 process — DONE 2026-07-10 - **Spec:** [agent-scale.md](agent-scale.md) slice A (HELD) -- **Landed:** `tools/claim.sh` (claim/list/status/release/check), - gitignored `.agents/claims/`, AGENT + prompts contract. +- **Landed:** `tools/claim.sh` (compatibility name; activity + record/list/status/release/check), gitignored `.agents/claims/`, AGENT + + prompts contract. Overlap warns and proceeds; only Rust-gate and final + landing phases are serialized. ### P2. Append-only ledgers / generated indexes 🟩 process — DONE 2026-07-10 - **Spec:** [agent-scale.md](agent-scale.md) slice C (HELD) @@ -770,7 +773,7 @@ ### P3. Worktree bootstrap + prune (+ sccache docs) 🟩 process — DONE 2026-07-09 - **Spec:** [agent-scale.md](agent-scale.md) slice D (HELD) - **Landed:** `tools/worktree-new.sh`, `tools/worktree-done.sh` (seed, - claim, prune target/, remove worktree), one `.Codex/worktrees` default + activity record, prune target/, remove worktree), one `.Codex/worktrees` default across agent surfaces, `MISALIGNED_WORKTREE_ROOT` override, sccache note in workflows. @@ -805,7 +808,7 @@ live ROADMAP index; human/JSON `project-status`; metadata/prose consistency fixtures in local + CI docs gates; three deterministic agent scenarios with evidence bundles; safe `task.sh` lifecycle; `doctor.sh`; durable shared - claims/heartbeats across worktrees. + activity/heartbeats across worktrees. --- diff --git a/wiki/process/agent-scale.md b/wiki/process/agent-scale.md --- a/wiki/process/agent-scale.md +++ b/wiki/process/agent-scale.md @@ -3,13 +3,13 @@ ``` Type: spec Status: IMPLEMENTED -Status note: slices A–K are held as of 2026-07-10: claims and shared +Status note: slices A–K are held as of 2026-07-10: advisory activity and shared worktree/run state; package-aware fast/land verification; generated ledgers; worktree bootstrap/prune; corpus engine; hidden-window Bevy evidence; heartbeats; spec-owned work-order metadata plus generated ROADMAP status; human/JSON project status; dispatch-consistency fixtures; deterministic - agent scenarios; safe task lifecycle wrapper; and read-only environment - doctor. + agent scenarios; safe task lifecycle wrapper with serialized final landing; + and read-only environment doctor. Stage: Process Work order: project-operations Work priority: 5 @@ -56,8 +56,9 @@ | Layer | Decision | |---|---| -| Semantic mutex | Work orders **claim** exclusive keys; overlapping active claims are forbidden | +| Task visibility | Work orders advertise likely paths; overlap warns but never blocks isolated worktrees | | CPU mutex | At most one full Rust gate on the machine (lock — already in check.sh) | +| Landing mutex | One short task-finish lock covers final rebase, land gate, merge, and push | | Verification | Mid-loop **narrow** tier; once **land** gate; CI fast corpus plus change-aware Rust | | Ledgers | Append-only uniquely named files; generated indexes, not hand-edited tips | | Disk | Private package outputs per worktree; shared **dependency** cache only | @@ -66,58 +67,73 @@ | Bevy evidence | Headless/offscreen land proof, not only interactive windows | | Observability | Heartbeat files so "is it stuck?" is answerable | | Dispatch truth | Specs own structured work metadata; ROADMAP receives a generated live index | -| Project status | One human/JSON command combines work orders, claims, runs, worktrees, issues, and freshness | +| Project status | One human/JSON command combines work orders, activity, runs, worktrees, issues, and freshness | | Scenario evidence | Deterministic agent scripts emit transcripts and machine-readable evidence | | Task lifecycle | One safe wrapper starts, checks, lands, or abandons a metadata-backed work order | | Environment | One doctor command reports missing required and optional local capabilities | -## 1. Dispatch claims (semantic exclusivity) +## 1. Dispatch activity (advisory overlap) ### Behavior Before an agent starts repository work that can edit code or binding wiki -pages, it **claims** a work order: +pages, it records work-order **activity**: - **id** — worktree/task name (e.g. `dark-frame`) - **class** — `docs` | `frontend` | `sim` | `save` | `process` -- **exclusive_keys** — paths or logical keys that must not be co-owned while - the claim is active (examples: `src/bin/bevy.rs`, `src/save.rs`, - `wiki/interface/material-dark-frame.md`, package names once the workspace - lands) -- **status** — `claimed` → `checking` → `landing` → `done` (or `abandoned`) +- **exclusive_keys** — legacy metadata name for likely edit surfaces (for + example `crates/misaligned-bevy/src/main.rs`, + `crates/misaligned-core/src/save.rs`, or a binding wiki page). These paths + are collision information, not ownership. +- **status** — `claimed` → `checking` → `landing` → `done`, with `blocked` + and `abandoned` available when the task is not progressing. -A second claim whose exclusive_keys intersect an active claim must not -proceed (fail fast with a readable message). Docs-only claims with empty or -log-only keys may run widely in parallel. +A second activity record whose paths intersect an active task **warns and +proceeds**. Worktrees isolate edits; Git exposes textual collisions; the +corpus, tests, and landing agent resolve semantic collisions against current +main. Hard path exclusion was retired after it created a wait cycle on +2026-07-10: action-contract held `main.rs` while waiting for camera-exposure +work that needed the same file. A blocked task must never stop its own +prerequisite. -Storage (landed): machine-local gitignored files +Only scarce phases are serialized: the machine-wide Rust gate and the short +`task.sh finish` landing phase. The ROADMAP rule of at most one sim+save-heavy +work order remains an explicit dispatch constraint rather than a path lock. + +Storage keeps the historical machine-local gitignored path `.agents/claims/.claim` (override with `MISALIGNED_CLAIMS_DIR`). -Discover with `tools/claim.sh list`. Holder pid is the calling agent/shell +The `claim.sh` filename and `claim` verb are compatibility interfaces for +older worktrees; `activity` is the current verb. Discover with +`tools/claim.sh list`. +Holder pid is the calling agent/shell (`PPID`, or `MISALIGNED_CLAIM_PID`). Dead holders are reaped on the next claim/list. No Tangled auth required. ```bash -tools/claim.sh claim dark-frame --class frontend --key src/bin/bevy.rs \ +tools/claim.sh activity dark-frame --class frontend --key crates/misaligned-bevy/src/main.rs \ --key wiki/interface/material-dark-frame.md tools/claim.sh list +tools/claim.sh set-status dark-frame blocked # visible, never a path lock tools/claim.sh set-status dark-frame checking # or landing tools/claim.sh release dark-frame # done (or --status abandoned) # bundled with worktree create/remove: -tools/worktree-new.sh dark-frame --class frontend --key src/bin/bevy.rs +tools/worktree-new.sh dark-frame --class frontend --key crates/misaligned-bevy/src/main.rs tools/worktree-done.sh dark-frame ``` -ROADMAP conflict flags (🟥 sim+save / 🟧 sim / 🟩 isolated) remain the human -index; claims enforce them mechanically for hot paths. +ROADMAP conflict flags (🟥 sim+save / 🟧 sim / 🟩 isolated) remain the dispatch +index. Path activity predicts reconciliation cost; it does not prohibit work. ### Acceptance criteria (slice A) — HELD 2026-07-09 -1. A documented claim procedure exists (tool or script + AGENT.md / - prompts pointer) that records class + exclusive_keys + status. -2. Attempting a second overlapping active claim fails with a message naming - the holder and keys. -3. Completing or abandoning a work order clears the claim. -4. ROADMAP or workflows.md states that agents must claim before heavy work. +1. A documented activity procedure exists (tool or script + AGENT.md / + prompts pointer) that records class + likely paths + status. +2. Attempting a second overlapping activity succeeds with a warning naming + the other task and paths; it never creates a wait dependency. +3. `blocked` is representable, and completing or abandoning work clears its + activity record. +4. ROADMAP or workflows.md states that agents inspect overlaps, work in + isolated worktrees, and reconcile at landing. ## 2. Verification phases (fast vs land) @@ -208,9 +224,9 @@ ### Behavior (landed helpers) ```bash -tools/worktree-new.sh --class frontend --key src/bin/bevy.rs -# -> .Codex/worktrees/ on worktree-, seed-cargo-target, claim -tools/worktree-done.sh # release claim, rm target/, remove worktree +tools/worktree-new.sh --class frontend --key crates/misaligned-bevy/src/main.rs +# -> .Codex/worktrees/ on worktree-, seed-cargo-target, activity +tools/worktree-done.sh # clear activity, rm target/, remove worktree ``` `.Codex/worktrees` is the one repository default for every agent surface. @@ -309,8 +325,9 @@ Every non-IMPLEMENTED spec is a dispatchable or intentionally staged work order and declares the structured fields owned by -[meta.md](meta.md#work-order-metadata): task slug, priority, claim class, hard -blockers, and exclusive keys. `Status:` remains the lifecycle source of truth; +[meta.md](meta.md#work-order-metadata): task slug, priority, work class, hard +blockers, and likely edit surfaces (the legacy `Exclusive keys` field). +`Status:` remains the lifecycle source of truth; the work-order fields must not duplicate it. `tools/work_orders.py` validates that metadata and generates the live @@ -322,7 +339,7 @@ combines, without mutating the repository: - ready, active, blocked, and staged work orders in priority order; -- active claims and heartbeat/run state; +- advisory activity and heartbeat/run state; - every worktree's dirty, ahead/behind, branch, and last-commit age; - Tangled decision issues when the local CLI is available/authenticated; - generated-index freshness and a short recommended-next-lane read. @@ -334,11 +351,11 @@ ### Acceptance criteria (slice H) — HELD 2026-07-10 1. Every non-IMPLEMENTED spec carries valid work-order metadata, and malformed - class, priority, blocker reference, task slug, or exclusive path fails the + class, priority, blocker reference, task slug, or advertised path fails the docs gate. 2. One generator writes/checks a clearly marked live region in ROADMAP from those fields; changing a spec status or blocker without regeneration fails. -3. `tools/project-status.py` reports work orders, claims, runs, worktrees, +3. `tools/project-status.py` reports work orders, activity, runs, worktrees, optional decision issues, and generated freshness in both human and JSON forms without changing repository state. 4. At least one fixture proves a blocked spec cannot be recommended as the @@ -358,7 +375,7 @@ - an `[OPEN]` section does not retain entries explicitly marked resolved; - an acceptance criterion cannot remain locally marked blocked on an issue that the same current page declares closed; -- exclusive path keys exist, so retired workspace paths cannot silently +- advertised path keys exist, so retired workspace paths cannot silently remain dispatch locks. Semantic design disagreements still belong to ticks; the gate must not guess @@ -414,18 +431,20 @@ tools/task.sh abandon project-operations ``` -`start` resolves task/class/exclusive keys from the spec and creates the -seeded claimed worktree. `check` moves the claim/heartbeat to checking and -runs the land gate. `finish` requires a clean committed branch, rebases onto +`start` resolves task/class/likely paths from the spec and creates the seeded +worktree with an activity record. `check` moves activity/heartbeat to checking +and runs the land gate. `finish` requires a clean committed branch, rebases onto current `origin/main`, reruns the land gate, fast-forwards a clean current primary checkout, pushes main, ends the heartbeat, and removes the task -worktree. A failed rebase, check, relationship check, or push stops before +worktree. One machine-local landing lock serializes that final +rebase/check/merge/push window. A failed rebase, check, relationship check, or +push stops before cleanup. It never commits, stashes, resets, force-pushes, or guesses through a conflict. `abandon` uses the existing clean-worktree refusal unless explicitly handled by the operator through the lower-level helper. `tools/doctor.sh` performs read-only checks for required commands, Cargo -workspace health, Tangled context/auth availability, worktree/claim state, +workspace health, Tangled context/auth availability, worktree/activity state, private-target safety, and optional accelerators such as sccache and the headless display path. Required failures exit nonzero; optional absences are plain warnings. @@ -434,7 +453,7 @@ 1. The wrapper starts a task using only its spec path and metadata and exposes the same project status command. -2. Check/finish update claim and heartbeat phases, and finish refuses dirty, +2. Check/finish update activity and heartbeat phases, and finish refuses dirty, uncommitted, divergent-primary, failed-gate, or rejected-push states before deleting anything. 3. Abandon preserves dirty work by default. @@ -442,6 +461,8 @@ no-network mode suitable for CI or offline work. 5. Shell syntax/fixture tests exercise argument handling and the destructive refusal paths without creating or pushing a real task branch. +6. The final landing lock refuses a live holder, reaps a dead holder, and + cannot be confused with advisory path activity. ## Relationship to the crate workspace @@ -449,11 +470,11 @@ work order. This page is the **coordination and tooling** work order. They reinforce each other but land separately. The crate workspace has landed; project-operations metadata and checks therefore use current `crates/` -package paths and reject retired monorepo claim keys. +package paths and reject retired monorepo activity paths. ## Implementation order (suggested, not binding) -1. Slice A (claims) — stops semantic collisions immediately +1. Slice A (activity) — makes likely collisions visible without blocking work 2. Slice C (ledgers) — stops landing union theater 3. Slice D (worktree bootstrap/prune) — disk 4. Slice B leftovers + G (land alias, heartbeats) — agent behavior @@ -464,8 +485,8 @@ ## Rejected alternatives -- **Only faster check.sh** — necessary but insufficient without exclusivity - and ledger shape. +- **Only faster check.sh** — necessary but insufficient without visible + activity, landing serialization, and ledger shape. - **Shared CARGO_TARGET_DIR across worktrees** — false-green risk. - **CI as the only gate** — local loop must stay honest. - **One mega-agent that owns main** — rejects the multi-agent premise of the diff --git a/wiki/process/meta.md b/wiki/process/meta.md --- a/wiki/process/meta.md +++ b/wiki/process/meta.md @@ -68,17 +68,20 @@ ``` - **Work order** is a unique lowercase task slug and becomes the default - worktree/claim id. + worktree/activity id. - **Work priority** is a positive integer; lower numbers run sooner within the current stage. Stage discipline still outranks the number, so a B3 priority `1` cannot jump ahead of unfinished B1 work. - **Work class** is exactly `docs`, `frontend`, `sim`, `save`, or `process`, - matching the claim helper. + matching the activity helper. - **Blocked by** is either `none` or a structured list of anchored `Type: spec` references, using the same repository-root syntax as `Depends on`. This field names only hard dispatch blockers; ordinary contracts remain in `Depends on`. -- **Exclusive keys** is a non-empty structured list of repository paths or - logical keys prefixed with `@`. Repository paths must currently exist. +- **Exclusive keys** is the legacy field name for a non-empty structured list + of likely edit surfaces: repository paths or logical keys prefixed with `@`. + Repository paths must currently exist. Overlap is advisory and may never + become a hard blocker; the name remains stable so existing spec metadata and + tooling do not require a corpus-wide schema migration. `Status:` remains the lifecycle owner; no separate dispatch-state field is allowed. `tools/work_orders.py` generates the live work-order region in diff --git a/wiki/process/workflows.md b/wiki/process/workflows.md --- a/wiki/process/workflows.md +++ b/wiki/process/workflows.md @@ -29,7 +29,7 @@ changes that can affect the executable, not every Markdown, log, process, or reference-art edit. Agents thrashing the machine with concurrent full gates is a process bug; the lock and auto-tiers below exist to stop that. The -broader multi-agent architecture (claims, ledgers, package workspace, +broader multi-agent architecture (advisory activity, ledgers, package workspace, heartbeats) lives in [agent-scale.md](agent-scale.md) and [crate-workspace.md](../engineering/crate-workspace.md). @@ -59,7 +59,7 @@ Do not hand-edit those two generated files. -**Claims and worktrees** (semantic exclusivity — see +**Activity and worktrees** (advisory overlap — see [agent-scale.md](agent-scale.md)): ```bash @@ -67,13 +67,13 @@ tools/claim.sh list tools/heartbeat.sh start dark-frame --phase implement ./tools/check.sh --land -tools/worktree-done.sh dark-frame # release claim, prune target/, remove worktree +tools/worktree-done.sh dark-frame # clear activity, prune target/, remove worktree ``` Specs with structured work-order metadata have a shorter safe doorway: ```bash -tools/project-status.py # live work + claims + worktrees + decisions +tools/project-status.py # live work + activity + worktrees + decisions tools/task.sh start wiki/interface/material-dark-frame.md tools/task.sh check material-dark-frame tools/task.sh finish material-dark-frame diff --git a/wiki/log/decisions/2026-07-10.md b/wiki/log/decisions/2026-07-10.md --- a/wiki/log/decisions/2026-07-10.md +++ b/wiki/log/decisions/2026-07-10.md @@ -400,3 +400,15 @@ otherwise unchanged body. Exact field constants remain `[TUNE]`. Owner: wiki/interface/thought-fluid.md material section; implementation lab: wiki/art/effects-lab.md. +- **2026-07-10 — Task paths advertise collision; they do not confer + ownership.** A live wait cycle proved the path-claim mutex had the wrong + boundary: action-contract held Bevy `main.rs` while waiting for + eyes-camera-exposure, which needed that same file, and downstream work then + waited on action-contract. DECIDED: overlapping work-order paths warn and + proceed in isolated worktrees. Git, the corpus, tests, and the landing agent + reconcile the result against current main. `blocked` is visible but never + excludes a prerequisite. Only scarce phases are hard locks: the shared Rust + gate and one final task landing (rebase/check/merge/push). The one-sim+save + rule remains dispatch law rather than path ownership. The compatibility + `claim.sh` name and `Exclusive keys` metadata survive so old worktrees and + the corpus schema interoperate, but both now mean advisory activity. -- tangled.sh