Skip to content

feat(chains): verify dev-loop pr handoff - #1009

Merged
aaronjmars merged 3 commits into
aeonfun:mainfrom
Svector-anu:feat/dev-loop-verified-handoff
Sep 1, 2026
Merged

feat(chains): verify dev-loop pr handoff#1009
aaronjmars merged 3 commits into
aeonfun:mainfrom
Svector-anu:feat/dev-loop-verified-handoff

Conversation

@Svector-anu

@Svector-anu Svector-anu commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

what

  • add a manual, provider-neutral dev-loop chain: feature then pr-review
  • require an explicit external:owner/repo[#issue] target
  • snapshot github's open-pr state before the feature run and verify exactly one new open pr from the authenticated actor afterward
  • bind that pr to the feature run with a machine-checked <!-- aeon-dispatch:<id> --> body marker
  • pass the verified owner/repo#number to pr-review, which now supports exact-pr scope
  • keep the unique dispatch-id correlation added in fix(chains): correlate dispatched skill runs uniquely #988

why

a successful feature harness exit is not proof that a pr exists. empty chain vars also let both skills fall back to broad watched-repository behavior, so the reviewer could inspect unrelated prs instead of the feature result.

the handoff now trusts github state plus a run-specific receipt, not the feature's closing prose: no pr skips review cleanly; multiple, unmarked, wrong-actor, missing, or malformed results fail closed.

verification

  • bash scripts/tests/test_dev_loop_handoff.sh — passed, including empty baseline, one, none, ambiguous, wrong-actor, concurrent-other, and unmarked concurrent-same-actor cases
  • bash scripts/tests/test_chain_runner.sh — 5 passed, 0 failed
  • bash scripts/tests/test_chain_when.sh — 18 passed, 0 failed
  • node --test scripts/validate-config.test.js — 14 passed
  • node scripts/validate-config.js — clean
  • readme/catalog and skill-pack registry suites — passed
  • eyebrow verify --ci — passed after canonical lock regeneration
  • git diff --check — clean

mutation proof for the empty-baseline regression: replacing the filename-based first-input check with NR == FNR made the empty-list case fail with feature created no new open PR; restoring the fix made the suite pass.

real end-to-end proof on the fork:

  • chain run 33520525541 completed successfully
  • feature run 33520550958 created Svector-anu/skopos#106
  • chain log recorded Verified feature PR: Svector-anu/skopos#106
  • review run 33521199398 reviewed only that pr and completed successfully

the chain is workflow-dispatch only. it does not enable either skill, pin a harness, choose a target, or merge a pr for the operator.

generated artifacts

current main contained pre-existing catalog and eyebrow drift from recently merged skill changes. the committed catalog refresh records those current skill revisions, and the canonical eyebrow v0.4.1 regeneration records all current-tree drift plus this pr-review change so the integrity gate evaluates the actual tree.

Comment thread scripts/dev-loop-pr.sh
gh pr list -R "$repo" --state open --limit 100 --json number,author --jq '.[] | "\(.number)\t\(.author.login)"' | sort -n > "$after"
actor_prs=$(awk -F '\t' -v actor="$actor" 'FILENAME == ARGV[1] { before[$1] = 1; next } !($1 in before) && $2 == actor { print $1 }' "$before" "$after")
actor_pr_count=$(printf '%s\n' "$actor_prs" | sed '/^$/d' | wc -l | tr -d ' ')
if [ "$actor_pr_count" -eq 1 ]; then

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[ISSUE] Accepting any single new same-actor open PR can hand off a concurrent impostor — if feature created none but another same-actor PR appeared during the run (manual create, overlapping skill), pr-review runs on the wrong PR and the chain still reports success. Why it matters: the chain’s purpose is exact feature→review handoff; set-difference alone is necessary but not sufficient under same-actor concurrency (you already reject count>1 and wrong-actor). Bind the candidate with createdAt >= snapshot time (or the feature dispatch timestamp), and add a concurrent-same-actor-impostor test beside concurrent-other.

@Svector-anu Svector-anu left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: discussion-needed — same-actor concurrent impostor can false-handoff

Findings (mirrored as inline comments):

  • [ISSUE] scripts/dev-loop-pr.sh:45 — accepting the sole new same-actor open PR without a createdAt/snapshot-time bind can attribute a concurrent impostor PR to feature when feature created none; pr-review then runs on the wrong PR while the chain reports success

@Svector-anu
Svector-anu force-pushed the feat/dev-loop-verified-handoff branch from b863b41 to 870d62c Compare September 1, 2026 15:53
@Svector-anu

Copy link
Copy Markdown
Contributor Author

fixed in 870d62ca.

the handoff no longer relies on author + set difference alone. chain-runner now passes its existing unique dispatch id into the feature prompt; feature records the exact <!-- aeon-dispatch:<id> --> marker in the pr body; and verify-new-pr requires that marker together with new/open/same-actor state before dispatching review.

added the requested concurrent-same-actor regression: a new pr from the same actor without this run's marker is rejected as no verified handoff. the existing empty-baseline, ambiguous, wrong-actor, and concurrent-other cases remain green.

Comment thread scripts/dev-loop-pr.sh
after=$(mktemp)
gh pr list -R "$repo" --state open --limit 100 --json number,author,body --jq '.[] | "\(.number)\t\(.author.login)\t\(.body // "")"' | sort -n > "$after"
actor_prs=$(awk -F '\t' -v actor="$actor" -v marker="<!-- aeon-dispatch:$dispatch_id -->" 'FILENAME == ARGV[1] { before[$1] = 1; next } !($1 in before) && $2 == actor && index($0, marker) { print $1 }' "$before" "$after")
actor_pr_count=$(printf '%s\n' "$actor_prs" | sed '/^$/d' | wc -l | tr -d ' ')

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[CRITICAL] Raw PR body is written into a line-oriented TSV — markdown bodies always contain newlines, so <!-- aeon-dispatch:… --> lands on a continuation line that fails $2 == actor, verify-new-pr exits 3, and chain-runner soft-succeeds with CHAIN_NO_ACTION while skipping review. Why it matters: the happy-path handoff this PR adds will silently no-op on every real feature PR (tests only use single-line bodies). Collapse newlines in jq (e.g. gsub("\n";" ")) or match the marker with jq/contains before line-splitting.

@Svector-anu Svector-anu left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: blocked: multiline PR bodies break dispatch-marker verify

Findings (mirrored as inline comments):

  • [CRITICAL] scripts/dev-loop-pr.sh:46 — serializing raw multi-line PR bodies into line-oriented TSV makes the dispatch-marker check miss every real feature PR, so verify exits 3 and the chain soft-succeeds without running pr-review

@Svector-anu
Svector-anu force-pushed the feat/dev-loop-verified-handoff branch from 870d62c to fff82f3 Compare September 1, 2026 16:02
@Svector-anu

Copy link
Copy Markdown
Contributor Author

fixed in fff82f3b.

verify-new-pr now keeps github's response as json and performs the baseline-number, actor, and multiline body-marker checks directly in jq; it no longer serializes pr bodies through a line-oriented tsv format.

the happy-path fixtures now use real multiline markdown bodies with the marker below other text. the full handoff suite passes, including the multiline marker, empty baseline, same-actor impostor, wrong-actor, ambiguous, and concurrent-other cases.

@Svector-anu Svector-anu left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: discussion-needed — feature PR templates omit the dispatch receipt the handoff now requires

Findings (mirrored as inline comments):

  • [ISSUE] skills/feature/SKILL.md:326 — B7 gh pr create --body template has no <!-- aeon-dispatch:<ID> --> placeholder (same gap in A8); agents that copy the nearby template skip the receipt, verify-new-pr soft-exits 3, and the chain reports success while leaving an unreviewed PR

Prior CRITICAL on SHA 870d62c (multiline TSV body break) is fixed in fff82f3 via jq-native JSON checks; marker bind still covers the earlier same-actor impostor case.

Comment thread skills/feature/SKILL.md
- build
- growth
---
When the run prompt supplies a `Workflow correlation ID`, include the exact marker `<!-- aeon-dispatch:<ID> -->` in every PR body you create. This is a machine-checked chain receipt: do not alter, omit, or place it only in the final response.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[ISSUE] This receipt requirement is not mirrored in the B7/A8 gh pr create --body templates (B7 at skills/feature/SKILL.md:326, A8 at :165) — under a long feature run the agent copies the nearby template, omits <!-- aeon-dispatch:<ID> -->, and verify-new-pr exits 3. Why it matters: the chain reports clean no-action success while leaving a real unreviewed PR, which is the handoff gap this change exists to close. Bake the marker into those templates when a Workflow correlation ID is present.

@Svector-anu
Svector-anu force-pushed the feat/dev-loop-verified-handoff branch from fff82f3 to 1986685 Compare September 1, 2026 16:12
@Svector-anu

Copy link
Copy Markdown
Contributor Author

fixed in 1986685d.

the dispatch receipt is now embedded directly in all three gh pr create body templates as ${AEON_DISPATCH_ID:+<!-- aeon-dispatch:$AEON_DISPATCH_ID -->}. it expands only for correlated chain runs and stays absent for ordinary standalone feature runs.

the handoff test also asserts that every feature pr template carries the conditional marker; all handoff and existing chain-correlation tests pass.

@Svector-anu Svector-anu left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: approve-ready — no blockers.

@aaronjmars

Copy link
Copy Markdown
Collaborator

The machine-checked feature -> PR bind is good. Do not merge yet: the review half of the loop can no-op the only PR the chain exists to review.

Blocker: pr-review still skips aeonframework.
skills/pr-review/SKILL.md now says ${var}=owner/repo#N reviews that exact PR, but the skip rules still drop author aeonframework (and [bot]). Feature PRs from this fleet are that account (GH_GLOBAL). Exact-PR support is four prose lines; the procedure still gh pr lists and applies the skip. When ${var} is owner/repo#N, fetch with gh pr view and do not apply the aeonframework skip.

Also: the dispatch receipt in skills/feature/SKILL.md is a bash expansion.
${AEON_DISPATCH_ID:+<!-- aeon-dispatch:$AEON_DISPATCH_ID -->} is not envsubst'd. An agent that pastes the template writes the unevaluated string, verify-new-pr soft-exits 3, the chain reports success, and the PR sits unreviewed. Put a literal <!-- aeon-dispatch:<Workflow correlation ID> --> in the three gh pr create templates.

CI is green. The GitHub-state bind (snapshot, marker, same-actor, fail-closed on ambiguous) is the right shape. The two items above are what make the loop actually review the PR it just opened.

aaronjmars and others added 2 commits September 1, 2026 15:15
…d-handoff

# Conflicts:
#	catalog/packs.json
#	catalog/skills.json
#	eyebrowlock.json
@aaronjmars
aaronjmars merged commit c92fbfb into aeonfun:main Sep 1, 2026
7 checks passed
@Svector-anu
Svector-anu deleted the feat/dev-loop-verified-handoff branch September 1, 2026 20:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants