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
59 changes: 49 additions & 10 deletions skills/orchestrate/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -218,10 +218,18 @@ answered.
earlier rounds satisfy `expected=<N>` immediately and the wait spins. Orca:
`scripts/orca-wait.sh` with the implement Task ids, already event-driven.
5. On wake, handle that task: review each worktree diff (`git -C <wt> diff
<integ>...HEAD`). If tests weak, audit with `test-quality-auditor`. On
approval, return to step 1 — whatever dependency it released shows up in the
next `ready-set.sh` round and the freed slot refills immediately. On rework
needed, write `reviews/<task>-rN.md` and re-deliver with `send-prompt.sh
<integ>...HEAD`). If tests weak, audit with `test-quality-auditor`. **On
approval, merge before you loop (issue #90):** fast-forward first —
`git fetch . <branch>:<integ>` (no checkout needed, works while the main
worktree sits on another branch) — and only fall back to
`scripts/safe-cleanup.sh merge <root> <integ> <branch>` when that is not
fast-forwardable (that verb DOES check out `<integ>` in the main worktree).
Either way, verify the merge landed with `git merge-base --is-ancestor
<branch> <integ>` before dispatching any dependent — that exit code is the
evidence, not the merge command's own chatter. Then return to step 1:
whatever dependency it released shows up in the next `ready-set.sh` round
already merged, and the freed slot refills immediately. On rework needed,
write `reviews/<task>-rN.md` and re-deliver with `send-prompt.sh
send` (or new Orca Task on same terminal); after 3 failed rounds, escalate.
When `ready-set.sh` returns **5**, go to Phase 5.

Expand Down Expand Up @@ -383,14 +391,40 @@ reasoning-effort flags) that `worker-start` cannot express.
resume) child it depends on — the real signature, not a paraphrase. This is the
contract the downstream session plans against; loose text invites drift. Wave 1
with no completed dependencies skips this.
1. `scripts/setup-worktrees.sh <integ> <root> <base> <branch>...` then verify with
`git worktree list`.

**What a `deps` edge delivers, and what it does not (issue #90).**
`graph.json` `deps` gate DISPATCH on the dependency's approval, and this step
injects its SIGNATURE — never its merged CODE. Merged code reaches a task's
worktree only through the merge-on-approval rule (step 5 below): a new
worktree branches from the integration branch's tip at the moment
`setup-worktrees.sh` runs (step 1), so it contains a dependency's code only
if that dependency was already approved and merged by then. A task that must
read merged results directly — realigning docs against another task's actual
diff, asserting an invariant that spans two tasks' code — cannot rely on the
signature alone: give it `deps` on every producer it reads, so it is not
dispatched until each producer is approved and merged.
1. `scripts/setup-worktrees.sh <integ> <root> <base> <branch>...` — each worktree
is created AT DISPATCH TIME, branched from the integration branch's CURRENT
tip; never pre-create a worktree for a future wave, or it misses whatever
merges land between now and that wave's dispatch. Then verify with `git
worktree list` — the script's own `base=<hash>` line on each new worktree
names what it actually branched from.
2. Per task: write `briefs/<task>.md` (templates/brief.md) — fill `<tools_guidance>`
from the resolved tool profile so the session uses the right knowledge/tacit
tools (the plan step is fixed to `wiki-plan`, not a configurable role), and for
a UI-facing task fill `<design_spec>` with the `design` role's pulled spec
(Phase 2) — then

The brief and plan are what the worker reads, not what it writes, so every
reference to them inside a composed prompt uses the `{ORCH_DIR}` token
(absolute path to this run's `.orchestration` dir, substituted like
`{STATUS_DIR}`) — the worker's cwd is its own worktree, which does not
contain `.orchestration/`. Repo files (source, tests, tracked docs) stay
relative to that cwd instead: an absolute repo path would make the worker
edit the main worktree rather than its own. This coordinator's own
`briefs/<task>.md` / `plans/<task>.md` references above stay relative — the
coordinator's cwd is the main repo root.

**2a. Plan it yourself, here, before launching.** Invoke the bundled `wiki-plan`
skill for this task and write the result to `plans/<task>.md`. Planning runs in
THIS coordinator session on purpose: a worker can be pinned to a cheaper tier
Expand Down Expand Up @@ -499,9 +533,11 @@ Run the fixed four-lens pass on each worktree diff (`git -C <wt> diff
Alongside the pass, if a session's tests look weak, **cross-call
`test-quality-auditor` yourself** (self-call + orchestrator cross-call).
On shortfall, write `reviews/<task>-rN.md`, inject §3 (rework), repeat. After 3
failed rounds, escalate. When a task is approved, return to step 1 of the dispatch
loop — whatever dependency it released shows up in the next `ready-set.sh` round and
the freed slot is refilled immediately. When `ready-set.sh` returns **5**, go to Phase 5.
failed rounds, escalate. When a task is approved, merge it into the integration
branch first (Phase 3 step 5's merge-on-approval rule) before you
return to step 1 of the dispatch loop — whatever dependency it released shows up in the next
`ready-set.sh` round already merged, and the freed slot is refilled immediately.
When `ready-set.sh` returns **5**, go to Phase 5.

**Insight emission.** After a rework round's fix is confirmed by re-review,
emit one ★ Insight candidate per finding that was fixed and confirmed —
Expand Down Expand Up @@ -580,7 +616,10 @@ Show the full integration diff (`git diff`). **Wait for the user's confirmation.

## Phase 6 — Cleanup + merge (only after Gate 2)
1. `scripts/safe-cleanup.sh merge <root> <integ> <branch>...` — refuses dirty
worktrees, merges sequentially, stops + reports on conflict (no --force).
worktrees, merges sequentially, stops + reports on conflict (no --force). A
branch already merged on approval (Phase 3 step 5, issue #90) re-merges here
as a no-op — git reports "Already up to date" — so this sweep stays correct
whether or not every branch was merged early.
2. `scripts/safe-cleanup.sh remove-worktrees <root> <branch>...` (after merge
verified; skips any dirty worktree).
3. `scripts/safe-cleanup.sh kill-sessions lo-<n>...` (exact names only), or — instead
Expand Down
3 changes: 2 additions & 1 deletion skills/orchestrate/scripts/setup-worktrees.sh
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,9 @@ for br in "$@"; do
echo "-> $br exists but is NOT based on $integ (possibly stale from a previous run) — verify before relying on it" >&2
fi
else
base_hash=$("$GIT" rev-parse --short "$integ")
"$GIT" worktree add -b "$br" "$path" "$integ"
echo "ok: worktree $path ($br)"
echo "ok: worktree $path ($br) base=$base_hash"
fi
# Scope guardrails inside this worktree (single source: worker-guardrails.sh —
# the Orca substrate calls the same script for an orca-created worktree).
Expand Down
66 changes: 63 additions & 3 deletions skills/orchestrate/scripts/watch-status.sh
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,47 @@ command -v "$TMUX_BIN" >/dev/null 2>&1 || TMUX_BIN=""
# script DISABLES the check (empty), mirroring the TMUX_BIN treatment above.
STALL_SCRIPT="${WATCH_STALL_SCRIPT:-$(dirname "$0")/tmux-worker-stalled.sh}"
[ -f "$STALL_SCRIPT" ] || STALL_SCRIPT=""

# classify_stall <session> — read the pane SCROLLBACK (not just the visible
# region) for a stalled session and print an annotation for the exit-7
# message, or nothing if no known pattern matches. Never fails the watch:
# every step degrades to "no annotation" on any capture/parse failure.
# Precedence: usage-limit wins over chooser (the chooser IS the limit
# chooser in the case this was built from).
classify_stall() {
[ -n "$TMUX_BIN" ] || return 0
cs_pane=$("$TMUX_BIN" capture-pane -t "=$1:" -p -S -1000 2>/dev/null) || cs_pane=""
[ -n "$cs_pane" ] || return 0

# ASCII-only ERE on purpose: the real message's "·" and curly quote are
# multibyte, and BSD/GNU grep + locale differences make matching them
# fragile. LO_LIMIT_EXTRA is an ADDITIVE fixed-string match, checked only
# when the default pattern misses — unset/empty means default-only, never
# an off switch.
cs_line=$(printf '%s\n' "$cs_pane" | grep -E "hit your (session|weekly) limit" | tail -n 1)
if [ -z "$cs_line" ] && [ -n "${LO_LIMIT_EXTRA:-}" ]; then
cs_line=$(printf '%s\n' "$cs_pane" | grep -F "$LO_LIMIT_EXTRA" | tail -n 1)
fi
if [ -n "$cs_line" ]; then
cs_tail=$(printf '%s\n' "$cs_line" | sed -n 's/.*resets \(.*\)/\1/p')
if [ -n "$cs_tail" ]; then
printf 'usage limit \302\267 resets %s' "$cs_tail"
else
printf 'usage limit'
fi
return 0
fi

# Chooser: last 30 lines only — it is live UI at the bottom, so a confirm
# hint deep in scrollback history is stale, not a pending chooser.
cs_last30=$(printf '%s\n' "$cs_pane" | tail -n 30)
if printf '%s\n' "$cs_last30" | grep -qF "Enter to confirm"; then
printf 'chooser pending \342\200\224 answer with send-prompt.sh keys'
return 0
fi
return 0
}

while [ "$elapsed" -lt "$budget" ]; do
# A worker's guardrails `ask`, recorded as an escalation, wakes the coordinator
# immediately rather than waiting out the timeout. The coordinator MUST resolve
Expand Down Expand Up @@ -153,12 +194,16 @@ while [ "$elapsed" -lt "$budget" ]; do
case ",$only," in *",$base,"*) : ;; *) continue ;; esac
fi
ph=$("$JQ" -r '.phase // "pending"' "$f" 2>/dev/null || echo "pending")
r=$(rank "$ph")
tk=$("$JQ" -r '.task // "?"' "$f" 2>/dev/null || echo "?")
summary="$summary $tk:$ph"
[ "$ph" = "failed" ] && { failed=$((failed+1)); continue; }
# dead-worker (zombie) detection: a non-terminal task whose tmux session is
# gone is treated as a failure, so the run aborts fast instead of waiting the
# whole timeout. Terminal phases are skipped (the session may legitimately end).
# This gate is UNCHANGED by the stall-skip gate below (#88): a vanished
# session at a reached-but-non-terminal phase (e.g. impl_done) is still
# reportable — reached-target only silences the STALL check, not liveness.
case "$ph" in
done|merged|approved) : ;;
*)
Expand All @@ -170,20 +215,35 @@ while [ "$elapsed" -lt "$budget" ]; do
# Per-session stall check: only rc 1 marks a stall. rc 0 (progressing),
# rc 2 (unknown), or a broken script are all NOT stalled — the explicit
# rc capture means no failure here can abort the watch loop.
if [ -n "$STALL_SCRIPT" ] && [ -n "$TMUX_BIN" ] && [ -n "$sess" ]; then
# Extra gate vs. the dead-worker check above: a task at/above the
# target is never stall-checked (#88) — its silence is exactly what
# the session prompt ordered ("signal and wait"), not a wedged worker.
if [ "$r" -lt "$target_rank" ] && [ -n "$STALL_SCRIPT" ] && [ -n "$TMUX_BIN" ] && [ -n "$sess" ]; then
src=0; sh "$STALL_SCRIPT" "$sess" >/dev/null 2>&1 || src=$?
if [ "$src" -eq 1 ]; then stalled="$stalled $tk:$sess"; fi
fi
;;
esac
r=$(rank "$ph")
[ "$r" -ge "$target_rank" ] && done_count=$((done_count+1))
done
echo "[watch ->$target] $done_count/$expected |$summary"
[ "$failed" -gt 0 ] && { echo "[watch] failed session detected — abort"; exit 3; }
[ "$done_count" -ge "$expected" ] && { echo "[watch] all reached $target"; exit 0; }
# Stall is the weakest signal: failed(3) and all-reached(0) above win over it.
if [ -n "$stalled" ]; then echo "[watch] worker stalled —$stalled"; exit 7; fi
if [ -n "$stalled" ]; then
stmsg=""
for se in $stalled; do
setk=${se%%:*}; sesess=${se#*:}
reason=$(classify_stall "$sesess")
if [ -n "$reason" ]; then
stmsg="$stmsg $setk:$sesess ($reason)"
else
stmsg="$stmsg $setk:$sesess"
fi
done
echo "[watch] worker stalled —$stmsg"
exit 7
fi
sleep "$interval"; elapsed=$((elapsed+interval))
done
echo "[watch] TIMEOUT (${budget}s, source=${budget_src}):$summary"; exit 2
9 changes: 6 additions & 3 deletions skills/orchestrate/templates/brief.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Brief template — XML task brief

The orchestrator fills this in per task and writes it to
`.orchestration/briefs/{TASK}.md`. Structure follows the delegation 4-part
`{ORCH_DIR}/briefs/{TASK}.md`. Structure follows the delegation 4-part
contract (objective / output / tools / boundaries) plus done + effort, in XML
tags so the session can re-recognize each section. Heavy context goes near the
top (long-context guidance); the session prompt's one-line trigger cites the
Expand Down Expand Up @@ -61,8 +61,11 @@ specific tags below as authority.
<!-- the plan is an INPUT, not an output: the coordinator ran `wiki-plan` on the
planning model and wrote it before this session launched. Adopt it; report a
gap rather than re-planning (re-planning would move the decisions onto the
worker's tier). -->
<plan>.orchestration/plans/{TASK}.md — written by the coordinator; adopt, verify against this brief, do not re-author</plan>
worker's tier). Orchestration artifacts (this brief, the plan, reviews) live
outside every worktree, so they are always addressed via {ORCH_DIR} (absolute);
repo files the worker edits stay relative to its own worktree cwd — an absolute
repo path would target the main worktree instead of the worker's own. -->
<plan>{ORCH_DIR}/plans/{TASK}.md — written by the coordinator; adopt, verify against this brief, do not re-author</plan>

<!-- output contract: how to signal completion -->
<output_contract>
Expand Down
Loading
Loading