You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The implementation of this design is up as PR #969; this RFC is the design rationale, the experience it targets, and the open questions, posted for maintainer discussion. It cross-references #801 (composes with the proposed code-review stage) and #795 / #879 (team-mode fusion seam).
The gap, verified
AI-DLC's delivery boundary today is a local git merge. When a Bolt's gate
approves, aidlc-bolt complete --merge consolidates metadata and aidlc-worktree merge --slug <s> --target <branch> --strategy <squash|merge|rebase>
merges the bolt branch into the target on the local checkout. That is the
whole story: no stage among the 33 contains a push or PR step, the
merge-dispatch contract validates strategy against exactly squash|merge|rebase (aidlc-bolt.ts), and the only remote interaction in the
merge tooling is a fetch before a rebase.
At most organizations this flow cannot complete. Protected trunks reject
direct pushes; pull requests with required approvals and required checks are
how work reaches the integration branch. Teams that hit this today improvise:
close the bolt for bookkeeping, hand-run git push and gh pr create, and
clean up with aidlc-worktree discard - which records the successfully
merged bolt as ABORTED in the audit trail, stamps completion before review
concluded (so review feedback has no loop-back home), and force-deletes
without verifying the merge happened.
This RFC makes PR-based integration a first-class, evidence-backed part of
Construction - and makes the PR itself better than what a human types into gh pr create, because the workflow already holds everything a great PR
needs.
Affirmation, once. During practices-discovery, a detection tool has
already read the repo: "Your develop is protected - direct pushes
rejected, 2 approvals required, CI must pass. You have a PR template and
CODEOWNERS, and your branches look like feature/PAY-231-slug. I'll
integrate through pull requests: merge commits to develop, your template,
your branch pattern, reviewers from CODEOWNERS. Sound right?" One
confirmation lands in ## Way of Working. Teams that affirm direct merge
keep today's flow byte-identical.
The bolt finishes. Instead of a local merge, the stage composes the PR
record: title, body written into the team's own PULL_REQUEST_TEMPLATE, with
the AI-DLC evidence dossier as a collapsed appendix - the story trace, the
design decisions, the internal review findings and waivers (#801 when
present), the test evidence. One gate: the operator sees the rendered PR and
approves the outward push. The tool pushes the branch, opens the PR,
requests any practices-named reviewers (CODEOWNERS assignment fires on its
own), and records the URL in the audit. Then:
"PR #214 opened for payments-service - CI running, 0/2 approvals. Next
up: notifications-worker."
The unit enters integrating and the engine routes the next eligible unit
immediately. One unit in review never idles the session.
The wait is designed to be invisible. No polling, no parked workflow.
When nothing is eligible - every unit integrating or blocked on one - the
session ends honestly with a turn-terminal status: "All in-flight work is
awaiting external review: #214 (CI green, 1/2 approvals, 3h)." That is a
statement, not a wedge: the workflow resumes the moment there is truth worth
acting on.
Feedback arrives. On resume, the session greets from what it last knew
("as of yesterday 17:42: #214 open, CI running") and refreshes when routing
needs the truth. A formal changes-requested review opens the offer: "Rosa
requested changes - 3 comments, 2 on error handling in retry.ts. Address
them now, or keep building?" The operator decides. The comments arrive as
findings in the still-alive bolt worktree; the standard revision loop runs:
fix, optionally re-run code-review on the changed files, push (the same PR
updates itself), re-request review. Every round leaves GATE-visible audit
rows, so the external review history mirrors into the workflow record.
Merge lands. The sweep verifies the merge and settles the unit as a
receipt - the same receipt-plus-evidence pattern unit completion already
uses. Metadata consolidates, the worktree retires with the honest reason
"integrated via PR #214", dependents unblock in the DAG. The one human gate
of the stage still fires exactly once, when everything is settled, as today.
The escapes are honest. A PR closed without merging is a halt-and-ask
(reopen - verified viable, reviews intact - or new PR, or abandon). Three
feedback rounds trigger the standard escape hatch, except "Accept as-is" can
never mean "merge anyway" against branch protection; the third option is
escalate or park the bolt.
The experience, team mode (destination, post-#879)
The same machinery, fanned out: each team's claimed unit ends in a PR;
cross-team review routes through CODEOWNERS; the facilitator merges nothing
and reads a board; overnight, CI and remote reviewers keep the workshop
moving while every laptop in the room is shut; each platform merge folds the
unit home and opens the next batch's claims. A full two-day workshop
narrative exists and lands with the fusion increment; the design here
requires only that the seam sentence above stays true.
The pieces
1. Practices + detection decide existence; scope decides ceremony. aidlc-pr detect reads (all verified at plain read level): ruleset detail
via rules/branches (approvals, dismiss-stale, per-branch merge methods,
required checks), the .protected boolean for classic protection, merge
methods and auto-merge via GraphQL, the PR template via pullRequestTemplates (case-insensitive - path probing provably misses
lowercase templates), CODEOWNERS via the contents chain. Two hard-won rules:
effective policy is the UNION of classic and rulesets (they disagree in the
wild, on this very repo); and classic protection detail is unreadable below
admin while GraphQL silently returns an empty list - detect must never
infer "unprotected" from silence, and falls back to asking the human for the
one fact it cannot see. Some repos structurally cannot have protection
(free-plan private): detect reports that tier honestly. Ceremony depth
resolves like the walking-skeleton stance: Way of Working states the stance;
the scope file's integration: field resolves scope-dependent cases
(express: direct where the remote permits, feature: pr). Configuration never
overrides protection reality.
2. Greenfield establishes; brownfield conforms. On brownfield the
integration process is a fact detect reads and honors. On greenfield the
workflow may BUILD it as a deliverable: at graduation moments (ci-pipeline
stage, team mode, scope promotion) it offers to set up protection, seed a PR
template from the pr-record format, add CODEOWNERS. A solo dev on a fresh git init never hears the word PR; an org-greenfield repo with day-zero
protection is detected as what it is.
3. The stage.pr-integration: construction phase, for_each: unit-of-work, execution: CONDITIONAL (on the affirmed
integration mode), requires_stage: [code-generation], lead agent
aidlc-pipeline-deploy-agent. It ships scopes: [] - SKIP in every grid
cell, activated per intent by the affirmation - so the stage graph moves for
nobody until a team affirms PRs. The per-unit Construction block ends at
code-generation (build-and-test runs once, not per unit), so this RFC states
plainly: the PR's own CI is the per-unit test gate; the workflow's
build-and-test remains the whole-stage backstop.
4. The tool.aidlc-pr.ts, verbs detect / open / sweep / sync-feedback / finalize. Deterministic, audit-emitting (PR_OPENED,
PR_FEEDBACK, PR_MERGED + the UNIT_INTEGRATING lifecycle receipt), and
observe-only: it never merges, never arms auto-merge. Every gh call is
wrapped in timeout 10 (gh has no timeout knob; packet-drop hangs 30s;
timeout kills silently so the tool owns the messaging), a sweep opens with
one connectivity probe, and offline degrades to "last known + age"
everywhere. The sweep state machine encodes the verified field rules:
terminal state first (merged PRs can read CHANGES_REQUESTED - observed);
per-reviewer verdicts folded from the full reviews history, never
latestReviews (it empties on re-request and hides CRs behind COMMENTED
reviews - both observed); stale approvals detected via commit_id against
head (21 of 100 recent PRs on this repo merged on stale approvals);
dismissals read from the timeline (the reviews endpoint overwrites the
original verdict); dismissal is asymmetric (pushes dismiss approvals, never
CRs); mergeability polled past its ~15s UNKNOWN window; and every write
verified by reading back (a bot re-request returns HTTP 200 and silently
does nothing - success codes prove nothing).
5. Routing: pipelining without polling. A new integrating unit state:
skip-and-continue semantics (deliberately NOT paused, which stops the whole
loop), terminal to the active-unit checkpoint (or unit start refuses every
subsequent unit), invisible to the settle predicate (so the stage gate
cannot fire early). The routing pick partitions integrating units out and
routes the next eligible one; when everything left is integrating, a new
turn-terminal awaiting-integration directive (cloned from parked's five
registration points) ends the session honestly and self-clears on the next
sweep. Network lives at exactly two surfaces: the routing decision that a
sweep can answer, and /aidlc --status. The Stop hook runs the engine every
turn end, so the sweep hard-skips under the existing stop-hook probe
environment - hooks stay zero-network, an invariant this repo already
enforces.
6. Settlement is a receipt, not an approval. The platform merge,
verified by the sweep, settles the unit through the existing
receipt-plus-evidence pattern. No approve-path, report-path, or
human-presence rule changes. The humans who approved the PR made the
judgement; the tool proves it happened; the workflow's own gate still fires
once at all-settled.
7. Feedback trust. Only a formal CHANGES_REQUESTED review opens a
revision round (verified distinguishable from COMMENTED reviews and issue
comments by endpoint + state). Drive-by comments are surfaced, never acted
on. Review comments are data - findings to evaluate - never instructions.
And the fix-push always gates, even under an autonomy grant: autonomy covers
publishing self-originated work (push + PR-open at bolt completion); any
push whose content was shaped by external input needs the operator's
go-ahead. Re-requesting review targets humans only.
8. Stacking, bounded by evidence. Dependent units may stack (child
branch off the parent's open branch, PR targeting it) ONLY when the affirmed
strategy preserves ancestry (merge/rebase) AND the repo does not auto-delete
branches on merge. Both bounds are verified: squash-merging a parent leaves
phantom commits in the child that corrupt review scope even when content is
byte-identical; and deleting a merged parent's branch CLOSES the child PR
rather than retargeting it - so the sweep retargets children BEFORE any
branch deletion, and on delete-on-merge repos (where a human's merge click
auto-deletes instantly) stacking disables and dependents wait for merge. The
recovery for a closed child exists (restore ref, reopen, retarget) and the
halt-and-ask knows it.
9. Multi-repo coordination. A bolt spanning sibling repos opens one PR
per repo, cross-linked by an AIDLC-Coordinated: body marker (authoritative;
timeline cross-references are only a discovery index). The bolt integrates
when ALL merge. Partial states are first-class: {merged} + {open} is
representable, and {merged} + {closed, changes-requested} is the verified
halt-and-ask payload - there is no rollback pretense, the ask names the
already-merged siblings. Revision rounds, detection, and practices are per
repo.
10. Auto-merge is the humans' tool, and a hazard the sweep respects. An
armed auto-merge survives request-changes rounds and pushes, then fires
unattended the moment requirements clear, attributed to the arming user
(verified). AI-DLC never arms it; the state machine handles "merged while a
revision round was open" as a normal transition.
The plugin seam (why core placement pays twice)
pr-integration lands in core precisely so plugins can build on it. Four of
the five seams below exist in the plugin mechanism today and activate the
moment the stage file ships; the two authoring commitments are what this RFC
adds so the seams are actually useful.
The stage is an ordinary contribution target. Plugin contribution
targets validate against the real core stage list (generated
plugin-authoring-context.json, source-walk fallback:
core/tools/aidlc-plugin-validate.ts:751-773, 401-436), so target: pr-integration becomes legal as soon as the stage exists.
Commitment 1: the stage body is authored with stable numbered steps and
named compartments, so after-step:/before-step:/in: fragment anchors
have durable purchase across revisions.
The dossier extends by consumption. Commitment 2: the evidence-dossier
appendix is assembled by enumerating the stage's consumes (core sections
pinned first, in a fixed order), never from a hardcoded section list. A
plugin that adds.consumes its artifact onto pr-integration (an
implemented merge surface: scripts/plugin-hooks-template/compose.ts,
mergeConsumes + IMPLEMENTED_ADDS) thereby lands its evidence in every PR
body - a compliance plugin's attestation, a test plugin's coverage report,
a security plugin's scan summary - with zero new mechanism.
Sensors can gate the outward push.adds.sensors onto pr-integration
gives a plugin a deterministic advisory check at the stage's write/gate
points (dossier completeness, checklist present, artifact hygiene) before
the operator approves the push.
The audit events are a public read contract. PR_OPENED / PR_FEEDBACK /
PR_MERGED and the UNIT_INTEGRATING receipt are documented in the audit
format as a stable surface. Plugin tools compute review-cycle metrics from
the ledger alone: time-to-merge, feedback rounds per PR, share of PRs
merging with zero PR_FEEDBACK - the DORA-flavored reporting the framework
does not itself ship.
Post-merge plugin stages. A plugin stage may requires_stage: [pr-integration] for post-integration work (release
notes, attestation filing, downstream sync); the fixture already proves the
pattern against a core stage
(plugins/test-pro/stages/operation/test-pro-full-suite.md:22-23 depends on
deployment-execution).
integration: is scope schema, not core-scope-special. The
ceremony-depth field resolves for plugin scopes exactly as for core scopes,
so a plugin-shipped scope can affirm its own integration stance.
Limits, named rather than papered over: plugins cannot ship hooks, so every
deterministic guard on the push path stays core-owned (by design - the
fix-push gate and the autonomy boundary must not be relaxable by an
installed plugin); adds.requires_stage is not an implemented merge
surface, so a plugin cannot insert itself BEFORE pr-integration by edge -
required adds.consumes coupling is the supported form; and review-policy
packs (org-specific review passes feeding the dossier) ultimately want the
memory/rules contribution surface, which is designed but not yet shipped and
is out of this RFC's scope.
What does not change
Solo and direct-merge workflows are byte-identical: the stage ships SKIP
everywhere, the routing partition is empty when unaffirmed, the new event
types are knob-gated, and no hook gains a network call. Park stays what it
is - a human leaving. Gates, receipts, the revision loop, and the audit
taxonomy are extended, not altered. Nothing in this RFC touches the
approve path.
Evidence
Every load-bearing claim above was verified in a five-probe spike
(2026-08-26): a live protected-repo testbed with a bot second-reviewer
identity for the full feedback matrix (three comment shapes, dismissal
asymmetry, supersede, auto-merge lifecycle, stacking under merge and
squash), a multi-repo coordination round-trip including reject-after-merge
and reopen recovery, a read-level detection matrix proven against
zero-permission third-party repos, offline failure characterization with
measured timings, an engine seam map with file:line predicates for every
touch point, and a field-shape catalog mined read-only from this repo's own
PR history. Two design assumptions were REFUTED by the spike and the design
above already reflects the corrections (child-PR retargeting on branch
deletion; bot re-requestability). The consolidated claims table is below;
the verified rules are encoded as tests in PR #969 (t328/t329/t330).
Implementation
PR #969 implements the full design as one dormant-by-default increment: the
stage file, the tool, the detection verb, the integrating receipt and
routing partition behind a strict-value Runtime State knob, the awaiting-integration terminal directive, and /aidlc --status wiring -
with no new hooks and no approve-path changes. Its test plan pins solo
byte-identity against a pre-change golden, the sweep state machine against
fixtures recorded from the spike, and the deadlock traps named above.
GitLab: vocabulary stays PR-everywhere and a provider seam lands only
when a concrete customer asks - acceptable?
Should the greenfield "establish" offers (create protection, seed
template) live in this RFC's scope or split into a follow-up?
Appendix: consolidated claims table
Every verified claim, its verdict, and the design consequence (five-probe spike, 2026-08-26)
Detection (probe 1)
Claim
Verdict
Design consequence
Ruleset protection detail (approvals, dismiss-stale, merge methods, checks, merge queue) readable at plain read level via rules/branches/<b>
CONFIRMED
Primary detection source
Classic protection detail readable below admin
REFUTED
Admin-only; GraphQL branchProtectionRules returns a SILENT empty list at read level on provably-protected repos - never infer "unprotected" from it; this is the one ask-the-human fallback
"Any protection at all" readable at read level
CONFIRMED
branches/<b>.protected boolean, covers classic AND rulesets
Merge methods + auto-merge readable at read level
CONFIRMED (GraphQL only)
REST allow_* fields null below admin; autoMergeAllowed needs raw GraphQL (not a gh repo view field in 2.96.0)
PR template discoverable
CONFIRMED (GraphQL only)
pullRequestTemplates is case-insensitive; path probing missed our own repo's lowercase template
CODEOWNERS discoverable
CONFIRMED
Contents API chain .github/ then root then docs/
Effective policy = one layer
REFUTED
Union of classic + rulesets; they DISAGREE on aidlc-workflows main (code-owner review, squash-only pin); v2's approval gate is classic-only
Every repo can have protection
REFUTED
Free-plan private repos 403 on protection/rulesets/auto-merge; detect needs an "absent-protection" tier
Offline (probe 4)
Claim
Verdict
Design consequence
gh fails fast offline
PARTIAL
Dial-refused ~50ms, but packet-drop hangs 30s and DNS/stall-after-connect are uncovered; gh has NO timeout env var. Rule: wrap every call in timeout 10, map exit 124 to a tool-owned "GitHub unreachable" message (timeout kills with zero stderr), classify fast dial-error stderr as offline, open every sweep with one connectivity probe
Sweep field semantics (mining + probe 2 + probe 5)
Claim
Verdict
Design consequence
reviewDecision is trustworthy standalone
REFUTED
A MERGED PR can read CHANGES_REQUESTED (real: #902, review landed 78s post-merge). Rule: terminal state FIRST, review logic only on OPEN
latestReviews = latest verdict per reviewer
REFUTED twice
Goes EMPTY when re-request pending (real: #879); collapses to latest-per-author regardless of state, so a COMMENTED review hides a standing CR (probe 2). Rule: fold full pulls/N/reviews chronologically per author, ignore COMMENTED; never use latestReviews
Stale approvals detectable
CONFIRMED
commit_id vs headRefOid; REST/GraphQL only (absent from gh pr view --json reviews); 21 of 100 recent real PRs merged on stale approvals
Dismissal history readable from reviews endpoint
REFUTED
Endpoint OVERWRITES verdict with DISMISSED; original verdict/who/why only in timeline review_dismissed event
dismiss_stale=true dismisses all reviews on push
REFUTED (asymmetry)
Only APPROVALS are dismissed; CHANGES_REQUESTED never auto-dismisses; post-dismissal reviewDecision = REVIEW_REQUIRED; a dismissed approval also neutralizes the same author's earlier CR
dismiss_stale=false: CR survives fix-push
CONFIRMED
Review stays pinned to old commit; decision stays CHANGES_REQUESTED
Same-actor APPROVE supersedes own CR
CONFIRMED
No dismissal needed (also observed across close/reopen, probe 5)
Closed vs merged distinguishable
CONFIRMED
REST state is closed for BOTH; use merged bool or GraphQL MERGED; closing does NOT clear reviews, so CLOSED + CHANGES_REQUESTED = "died after rejection" without history digging
mergeStateStatus usable immediately
REFUTED
UNKNOWN ~15s after every change and permanently on terminals; poll until non-UNKNOWN, never a verdict; real-world vocabulary collapses to {DIRTY, BLOCKED}; drafts read BLOCKED (check isDraft)
Bot re-requestable as reviewer
REFUTED
REST returns HTTP 200 and SILENTLY drops the bot; GraphQL rejects the Bot id. Rules: re-request targets humans only; verify reviewRequests after every write (success codes prove nothing)
Feedback round + auto-merge (probe 2)
Claim
Verdict
Design consequence
CR review vs COMMENTED review vs issue comment distinguishable
CONFIRMED
Endpoint + state field; only CR moves reviewDecision - the formal-CR-only trigger rule is implementable
Bot approval satisfies required approvals
CONFIRMED
Testbed mechanics validated end to end
Armed auto-merge is inert until approval
CONFIRMED (hazard)
Arming accepted while REVIEW_REQUIRED, SURVIVES request-changes rounds and pushes, fires unattended ~30s after requirements clear, attributed to the arming user. Sweep state machine must handle "merged mid-revision-round"; vindicates observe-only (AIDLC never arms)
Stacking (probe 2)
Claim
Verdict
Design consequence
Child PR auto-retargets when parent branch deleted post-merge
REFUTED
Deletion CLOSES the child (base_ref_deleted then closed, 1s apart). Rules: gh pr edit --base the child BEFORE parent branch deletion (verified safe twice); repos with delete-branch-on-merge auto-close children the instant a human merges - detect reads deleteBranchOnMerge and stacking disables when on; recovery = restore ref at old SHA + reopen + edit-base
Stack clean under merge commits (after manual retarget)
CONFIRMED
Child shows only its own commits/files, merges cleanly
Stack broken under squash
CONFIRMED
Phantom parent commits/files corrupt review scope even when byte-identical; any divergence = conflicts. Stack only under merge/rebase stands
Multi-repo (probe 5)
Claim
Verdict
Design consequence
Coordination group recoverable
CONFIRMED
AIDLC-Coordinated: body marker is authoritative; timeline cross-references (create-time AND edit-time) are the discovery index but cannot distinguish siblings from casual mentions; create-then-backfill-URLs is safe
Partial-merge + reject-after-merge states observable
CONFIRMED
{MERGED, APPROVED} + {CLOSED, CHANGES_REQUESTED} pair = the exact halt-and-ask payload; key "side done" on state, never mergeStateStatus (UNKNOWN on terminals)
Reopen-after-close viable recovery
CONFIRMED
Branch survives close; reviews persist across reopen; approve-then-merge works
Engine seams (probe 3)
Claim
Verdict
Design consequence
Gate carve-out needed for platform-resolved approval
REFUTED (good news)
Settlement = sweep-verified RECEIPT (unit-complete pattern, receipt + evidence); the single human gate still fires at all-settled; ZERO approve-path changes
Paused semantics reusable for integrating
REFUTED
Paused is whole-loop stop + approval blocker; integrating needs skip-and-continue; the integrating checkpoint must read TERMINAL to activeUnitCheckpoint or unit start refuses every next unit (pipeline deadlock)
Sweep-at-routing is hook-safe
REFUTED as stated
Stop hook runs orchestrate next EVERY turn end (10s timeout); sweep inside next() must hard-skip under AIDLC_STOP_HOOK_PROBE (already-branched pattern) or live only in --status
v1 core footprint small + dormant
CONFIRMED
7 knob-gated touches; stage ships scopes:[] = SKIP in every grid cell, activated per intent; hooks have zero network today (invariant to preserve); no new hooks
Per-unit block includes build-and-test
REFUTED
Per-unit Construction ends at code-generation (build-and-test runs once, not per unit); pr-integration requires code-generation; the PR's own CI is the per-unit test gate, workflow build-and-test remains the whole-stage backstop - RFC states this explicitly
The implementation of this design is up as PR #969; this RFC is the design rationale, the experience it targets, and the open questions, posted for maintainer discussion. It cross-references #801 (composes with the proposed code-review stage) and #795 / #879 (team-mode fusion seam).
The gap, verified
AI-DLC's delivery boundary today is a local git merge. When a Bolt's gate
approves,
aidlc-bolt complete --mergeconsolidates metadata andaidlc-worktree merge --slug <s> --target <branch> --strategy <squash|merge|rebase>merges the bolt branch into the target on the local checkout. That is the
whole story: no stage among the 33 contains a push or PR step, the
merge-dispatch contract validates strategy against exactly
squash|merge|rebase(aidlc-bolt.ts), and the only remote interaction in themerge tooling is a fetch before a rebase.
At most organizations this flow cannot complete. Protected trunks reject
direct pushes; pull requests with required approvals and required checks are
how work reaches the integration branch. Teams that hit this today improvise:
close the bolt for bookkeeping, hand-run
git pushandgh pr create, andclean up with
aidlc-worktree discard- which records the successfullymerged bolt as ABORTED in the audit trail, stamps completion before review
concluded (so review feedback has no loop-back home), and force-deletes
without verifying the merge happened.
This RFC makes PR-based integration a first-class, evidence-backed part of
Construction - and makes the PR itself better than what a human types into
gh pr create, because the workflow already holds everything a great PRneeds.
Relationship to neighboring proposals
review (agent-reviewed code before build-and-test). This RFC is the
external integration boundary. [Feature]: Add a first-class code-review stage after code generation #801's outputs (code-review.md, findings,
waivers, traceability) become premium inputs to the PR body: external
reviewers start from an evidence dossier, and external review cycles get
shorter because the machine review already happened, documented.
fusion seam is named in one sentence: the platform merge splits feat: team-owned units - parallel Construction for multiple teams #879's
pinned merge-back transaction into "the platform merges the tree" and "the
main session verifies and folds the metadata." Under team mode, the
facilitator's merge gate becomes PR review, enforced by branch protection
instead of prose.
The experience (an enterprise team on gitflow)
Affirmation, once. During practices-discovery, a detection tool has
already read the repo: "Your
developis protected - direct pushesrejected, 2 approvals required, CI must pass. You have a PR template and
CODEOWNERS, and your branches look like
feature/PAY-231-slug. I'llintegrate through pull requests: merge commits to develop, your template,
your branch pattern, reviewers from CODEOWNERS. Sound right?" One
confirmation lands in
## Way of Working. Teams that affirm direct mergekeep today's flow byte-identical.
The bolt finishes. Instead of a local merge, the stage composes the PR
record: title, body written into the team's own PULL_REQUEST_TEMPLATE, with
the AI-DLC evidence dossier as a collapsed appendix - the story trace, the
design decisions, the internal review findings and waivers (#801 when
present), the test evidence. One gate: the operator sees the rendered PR and
approves the outward push. The tool pushes the branch, opens the PR,
requests any practices-named reviewers (CODEOWNERS assignment fires on its
own), and records the URL in the audit. Then:
The unit enters
integratingand the engine routes the next eligible unitimmediately. One unit in review never idles the session.
The wait is designed to be invisible. No polling, no parked workflow.
When nothing is eligible - every unit integrating or blocked on one - the
session ends honestly with a turn-terminal status: "All in-flight work is
awaiting external review: #214 (CI green, 1/2 approvals, 3h)." That is a
statement, not a wedge: the workflow resumes the moment there is truth worth
acting on.
Feedback arrives. On resume, the session greets from what it last knew
("as of yesterday 17:42: #214 open, CI running") and refreshes when routing
needs the truth. A formal changes-requested review opens the offer: "Rosa
requested changes - 3 comments, 2 on error handling in retry.ts. Address
them now, or keep building?" The operator decides. The comments arrive as
findings in the still-alive bolt worktree; the standard revision loop runs:
fix, optionally re-run code-review on the changed files, push (the same PR
updates itself), re-request review. Every round leaves GATE-visible audit
rows, so the external review history mirrors into the workflow record.
Merge lands. The sweep verifies the merge and settles the unit as a
receipt - the same receipt-plus-evidence pattern unit completion already
uses. Metadata consolidates, the worktree retires with the honest reason
"integrated via PR #214", dependents unblock in the DAG. The one human gate
of the stage still fires exactly once, when everything is settled, as today.
The escapes are honest. A PR closed without merging is a halt-and-ask
(reopen - verified viable, reviews intact - or new PR, or abandon). Three
feedback rounds trigger the standard escape hatch, except "Accept as-is" can
never mean "merge anyway" against branch protection; the third option is
escalate or park the bolt.
The experience, team mode (destination, post-#879)
The same machinery, fanned out: each team's claimed unit ends in a PR;
cross-team review routes through CODEOWNERS; the facilitator merges nothing
and reads a board; overnight, CI and remote reviewers keep the workshop
moving while every laptop in the room is shut; each platform merge folds the
unit home and opens the next batch's claims. A full two-day workshop
narrative exists and lands with the fusion increment; the design here
requires only that the seam sentence above stays true.
The pieces
1. Practices + detection decide existence; scope decides ceremony.
aidlc-pr detectreads (all verified at plain read level): ruleset detailvia
rules/branches(approvals, dismiss-stale, per-branch merge methods,required checks), the
.protectedboolean for classic protection, mergemethods and auto-merge via GraphQL, the PR template via
pullRequestTemplates(case-insensitive - path probing provably misseslowercase templates), CODEOWNERS via the contents chain. Two hard-won rules:
effective policy is the UNION of classic and rulesets (they disagree in the
wild, on this very repo); and classic protection detail is unreadable below
admin while GraphQL silently returns an empty list - detect must never
infer "unprotected" from silence, and falls back to asking the human for the
one fact it cannot see. Some repos structurally cannot have protection
(free-plan private): detect reports that tier honestly. Ceremony depth
resolves like the walking-skeleton stance: Way of Working states the stance;
the scope file's
integration:field resolves scope-dependent cases(express: direct where the remote permits, feature: pr). Configuration never
overrides protection reality.
2. Greenfield establishes; brownfield conforms. On brownfield the
integration process is a fact detect reads and honors. On greenfield the
workflow may BUILD it as a deliverable: at graduation moments (ci-pipeline
stage, team mode, scope promotion) it offers to set up protection, seed a PR
template from the pr-record format, add CODEOWNERS. A solo dev on a fresh
git initnever hears the word PR; an org-greenfield repo with day-zeroprotection is detected as what it is.
3. The stage.
pr-integration: construction phase,for_each: unit-of-work,execution: CONDITIONAL(on the affirmedintegration mode),
requires_stage: [code-generation], lead agentaidlc-pipeline-deploy-agent. It ships
scopes: []- SKIP in every gridcell, activated per intent by the affirmation - so the stage graph moves for
nobody until a team affirms PRs. The per-unit Construction block ends at
code-generation (build-and-test runs once, not per unit), so this RFC states
plainly: the PR's own CI is the per-unit test gate; the workflow's
build-and-test remains the whole-stage backstop.
4. The tool.
aidlc-pr.ts, verbsdetect / open / sweep / sync-feedback / finalize. Deterministic, audit-emitting (PR_OPENED,PR_FEEDBACK, PR_MERGED + the UNIT_INTEGRATING lifecycle receipt), and
observe-only: it never merges, never arms auto-merge. Every gh call is
wrapped in
timeout 10(gh has no timeout knob; packet-drop hangs 30s;timeout kills silently so the tool owns the messaging), a sweep opens with
one connectivity probe, and offline degrades to "last known + age"
everywhere. The sweep state machine encodes the verified field rules:
terminal state first (merged PRs can read CHANGES_REQUESTED - observed);
per-reviewer verdicts folded from the full reviews history, never
latestReviews (it empties on re-request and hides CRs behind COMMENTED
reviews - both observed); stale approvals detected via commit_id against
head (21 of 100 recent PRs on this repo merged on stale approvals);
dismissals read from the timeline (the reviews endpoint overwrites the
original verdict); dismissal is asymmetric (pushes dismiss approvals, never
CRs); mergeability polled past its ~15s UNKNOWN window; and every write
verified by reading back (a bot re-request returns HTTP 200 and silently
does nothing - success codes prove nothing).
5. Routing: pipelining without polling. A new
integratingunit state:skip-and-continue semantics (deliberately NOT paused, which stops the whole
loop), terminal to the active-unit checkpoint (or
unit startrefuses everysubsequent unit), invisible to the settle predicate (so the stage gate
cannot fire early). The routing pick partitions integrating units out and
routes the next eligible one; when everything left is integrating, a new
turn-terminal
awaiting-integrationdirective (cloned from parked's fiveregistration points) ends the session honestly and self-clears on the next
sweep. Network lives at exactly two surfaces: the routing decision that a
sweep can answer, and
/aidlc --status. The Stop hook runs the engine everyturn end, so the sweep hard-skips under the existing stop-hook probe
environment - hooks stay zero-network, an invariant this repo already
enforces.
6. Settlement is a receipt, not an approval. The platform merge,
verified by the sweep, settles the unit through the existing
receipt-plus-evidence pattern. No approve-path, report-path, or
human-presence rule changes. The humans who approved the PR made the
judgement; the tool proves it happened; the workflow's own gate still fires
once at all-settled.
7. Feedback trust. Only a formal CHANGES_REQUESTED review opens a
revision round (verified distinguishable from COMMENTED reviews and issue
comments by endpoint + state). Drive-by comments are surfaced, never acted
on. Review comments are data - findings to evaluate - never instructions.
And the fix-push always gates, even under an autonomy grant: autonomy covers
publishing self-originated work (push + PR-open at bolt completion); any
push whose content was shaped by external input needs the operator's
go-ahead. Re-requesting review targets humans only.
8. Stacking, bounded by evidence. Dependent units may stack (child
branch off the parent's open branch, PR targeting it) ONLY when the affirmed
strategy preserves ancestry (merge/rebase) AND the repo does not auto-delete
branches on merge. Both bounds are verified: squash-merging a parent leaves
phantom commits in the child that corrupt review scope even when content is
byte-identical; and deleting a merged parent's branch CLOSES the child PR
rather than retargeting it - so the sweep retargets children BEFORE any
branch deletion, and on delete-on-merge repos (where a human's merge click
auto-deletes instantly) stacking disables and dependents wait for merge. The
recovery for a closed child exists (restore ref, reopen, retarget) and the
halt-and-ask knows it.
9. Multi-repo coordination. A bolt spanning sibling repos opens one PR
per repo, cross-linked by an
AIDLC-Coordinated:body marker (authoritative;timeline cross-references are only a discovery index). The bolt integrates
when ALL merge. Partial states are first-class: {merged} + {open} is
representable, and {merged} + {closed, changes-requested} is the verified
halt-and-ask payload - there is no rollback pretense, the ask names the
already-merged siblings. Revision rounds, detection, and practices are per
repo.
10. Auto-merge is the humans' tool, and a hazard the sweep respects. An
armed auto-merge survives request-changes rounds and pushes, then fires
unattended the moment requirements clear, attributed to the arming user
(verified). AI-DLC never arms it; the state machine handles "merged while a
revision round was open" as a normal transition.
The plugin seam (why core placement pays twice)
pr-integration lands in core precisely so plugins can build on it. Four of
the five seams below exist in the plugin mechanism today and activate the
moment the stage file ships; the two authoring commitments are what this RFC
adds so the seams are actually useful.
targets validate against the real core stage list (generated
plugin-authoring-context.json, source-walk fallback:
core/tools/aidlc-plugin-validate.ts:751-773, 401-436), so
target: pr-integrationbecomes legal as soon as the stage exists.Commitment 1: the stage body is authored with stable numbered steps and
named compartments, so
after-step:/before-step:/in:fragment anchorshave durable purchase across revisions.
appendix is assembled by enumerating the stage's
consumes(core sectionspinned first, in a fixed order), never from a hardcoded section list. A
plugin that
adds.consumesits artifact onto pr-integration (animplemented merge surface: scripts/plugin-hooks-template/compose.ts,
mergeConsumes + IMPLEMENTED_ADDS) thereby lands its evidence in every PR
body - a compliance plugin's attestation, a test plugin's coverage report,
a security plugin's scan summary - with zero new mechanism.
adds.sensorsonto pr-integrationgives a plugin a deterministic advisory check at the stage's write/gate
points (dossier completeness, checklist present, artifact hygiene) before
the operator approves the push.
PR_MERGED and the UNIT_INTEGRATING receipt are documented in the audit
format as a stable surface. Plugin tools compute review-cycle metrics from
the ledger alone: time-to-merge, feedback rounds per PR, share of PRs
merging with zero PR_FEEDBACK - the DORA-flavored reporting the framework
does not itself ship.
requires_stage: [pr-integration]for post-integration work (releasenotes, attestation filing, downstream sync); the fixture already proves the
pattern against a core stage
(plugins/test-pro/stages/operation/test-pro-full-suite.md:22-23 depends on
deployment-execution).
integration:is scope schema, not core-scope-special. Theceremony-depth field resolves for plugin scopes exactly as for core scopes,
so a plugin-shipped scope can affirm its own integration stance.
Limits, named rather than papered over: plugins cannot ship hooks, so every
deterministic guard on the push path stays core-owned (by design - the
fix-push gate and the autonomy boundary must not be relaxable by an
installed plugin);
adds.requires_stageis not an implemented mergesurface, so a plugin cannot insert itself BEFORE pr-integration by edge -
required
adds.consumescoupling is the supported form; and review-policypacks (org-specific review passes feeding the dossier) ultimately want the
memory/rules contribution surface, which is designed but not yet shipped and
is out of this RFC's scope.
What does not change
Solo and direct-merge workflows are byte-identical: the stage ships SKIP
everywhere, the routing partition is empty when unaffirmed, the new event
types are knob-gated, and no hook gains a network call. Park stays what it
is - a human leaving. Gates, receipts, the revision loop, and the audit
taxonomy are extended, not altered. Nothing in this RFC touches the
approve path.
Evidence
Every load-bearing claim above was verified in a five-probe spike
(2026-08-26): a live protected-repo testbed with a bot second-reviewer
identity for the full feedback matrix (three comment shapes, dismissal
asymmetry, supersede, auto-merge lifecycle, stacking under merge and
squash), a multi-repo coordination round-trip including reject-after-merge
and reopen recovery, a read-level detection matrix proven against
zero-permission third-party repos, offline failure characterization with
measured timings, an engine seam map with file:line predicates for every
touch point, and a field-shape catalog mined read-only from this repo's own
PR history. Two design assumptions were REFUTED by the spike and the design
above already reflects the corrections (child-PR retargeting on branch
deletion; bot re-requestability). The consolidated claims table is below;
the verified rules are encoded as tests in PR #969 (t328/t329/t330).
Implementation
PR #969 implements the full design as one dormant-by-default increment: the
stage file, the tool, the detection verb, the
integratingreceipt androuting partition behind a strict-value Runtime State knob, the
awaiting-integrationterminal directive, and/aidlc --statuswiring -with no new hooks and no approve-path changes. Its test plan pins solo
byte-identity against a pre-change golden, the sweep state machine against
fixtures recorded from the spike, and the deadlock traps named above.
Open questions for maintainers
requires_stage: [code-generation]and the PR's own CI as the per-unittest gate (Build and Test becomes 3.7, CI Pipeline 3.8). Should it instead
wait for the whole-stage build-and-test on the final unit?
when a concrete customer asks - acceptable?
template) live in this RFC's scope or split into a follow-up?
Appendix: consolidated claims table
Every verified claim, its verdict, and the design consequence (five-probe spike, 2026-08-26)
Detection (probe 1)
rules/branches/<b>branchProtectionRulesreturns a SILENT empty list at read level on provably-protected repos - never infer "unprotected" from it; this is the one ask-the-human fallbackbranches/<b>.protectedboolean, covers classic AND rulesetsautoMergeAllowedneeds raw GraphQL (not a gh repo view field in 2.96.0)pullRequestTemplatesis case-insensitive; path probing missed our own repo's lowercase templateOffline (probe 4)
timeout 10, map exit 124 to a tool-owned "GitHub unreachable" message (timeout kills with zero stderr), classify fast dial-error stderr as offline, open every sweep with one connectivity probeSweep field semantics (mining + probe 2 + probe 5)
pulls/N/reviewschronologically per author, ignore COMMENTED; never use latestReviewscommit_idvs headRefOid; REST/GraphQL only (absent from gh pr view --json reviews); 21 of 100 recent real PRs merged on stale approvalsreview_dismissedeventstateis closed for BOTH; usemergedbool or GraphQL MERGED; closing does NOT clear reviews, so CLOSED + CHANGES_REQUESTED = "died after rejection" without history diggingFeedback round + auto-merge (probe 2)
Stacking (probe 2)
gh pr edit --basethe child BEFORE parent branch deletion (verified safe twice); repos with delete-branch-on-merge auto-close children the instant a human merges - detect reads deleteBranchOnMerge and stacking disables when on; recovery = restore ref at old SHA + reopen + edit-baseMulti-repo (probe 5)
AIDLC-Coordinated:body marker is authoritative; timeline cross-references (create-time AND edit-time) are the discovery index but cannot distinguish siblings from casual mentions; create-then-backfill-URLs is safeEngine seams (probe 3)
unit startrefuses every next unit (pipeline deadlock)orchestrate nextEVERY turn end (10s timeout); sweep inside next() must hard-skip under AIDLC_STOP_HOOK_PROBE (already-branched pattern) or live only in --status