PR delivery and generations

Part of the reintegration policy. Its Markdown source is the same contract in plain text.

PR-backed integration

A project lands work through pull requests when its integration.backend is pull_request. Workers call caco agent reintegrate or caco agent complete without mode or backend overrides; the project's integration.default_intent and backend decide what happens. There is no queue engine and no daemon validation gate: GitHub branch protection and CI decide when a PR may merge.

Native GitHub auto-merge

With direct intent on the pull_request backend and native auto-merge enabled, the daemon pushes the agent's branch without force, opens or reuses its one PR against the configured base, and arms squash auto-merge on the pushed head, then returns to the worker, which moves on. GitHub runs the required checks; for Cacophony the only required check is Check and Lint. When they pass GitHub merges the PR, and the daemon's merged-worker reconcile sees the merge and closes the PR's beads.
PR delivery with native auto-mergeThe worker hands off at the armed PR; GitHub's required check decides the merge, and the reconcile closes the beads.

With native auto-merge enabled for the project (Cacophony sets CACO_PR_BACKEND_CI_AUTO_MERGE=1 in its project environment), direct intent pushes the owner's agent branch without force, opens or reuses its one PR against integration.pr_base, and arms squash auto-merge on the pushed head. GitHub merges the PR once its required checks pass; for Cacophony the only required check is Check & Lint. GitHub's current PR state and required checks are authoritative; cached target refs and local publication records neither grant nor veto publication. A later reintegration from the same owner updates the same PR rather than opening a new one. Once GitHub reports the merge, the merged-worker reconcile closes the PR's beads, so the owner moves on to its next task instead of watching the PR. See owned checkout publication for previews and retries.

Synchronous merge

Without native auto-merge, direct intent on the PR backend merges in the same call: it runs gh pr merge --squash, then verifies with gh pr view that the PR is MERGED and the merge commit is reachable from the base after a fresh fetch. An unmergeable PR reports success:false. A conflict it cannot rebase automatically is returned to the owner, who runs caco agent rebase, resolves it and retries.

Publish-only submission (pr_submit)

A project may configure pr_submit as its intent. Completion pushes the owner's work to a stable per-bead submission head, <agent-branch>-pr-<bead>, opens or updates one ready PR against integration.pr_base, records the PR on the beads (status merging) and completes the agent, keeping its checkout so the owner can be woken later. Caco does not merge the PR or wait for review. A later completion by the woken owner updates the same PR, force-with-lease only over a head that owner published or its checkout already contains; a head another actor advanced is refused as pr_submit_head_advanced until the owner integrates it. The provider-confirmed merge closes the beads through the merged-worker reconcile; squash-landed work is recognised by content on the target, so a completed owner becomes prunable once its PR merged and its beads closed.

Explicit PR modes

pr_review opens a PR and stops for review; it succeeds only with a reported PR URL and never claims the base branch landed. pr_auto_merge uses the same topology and reports the PR explicitly. Direct intent on a PR backend must prove the merge or an armed auto-merge; an unmerged PR is never silently treated as landed.

integration.pr_base may name an external upstream remote marked pr_target: true, not only the fork's own default branch. With that topology every PR mode opens its PR against the upstream base, with the agent's published fork branch as the head. upstream_draft_pr packages the same targeting as a dedicated intent that also keeps the fork's long-lived branch free of product code.

Publication rules

The PR backend uses the project's Git topology rather than a parallel GitHub configuration:

A successful zero-ahead result (nothing to reintegrate) is a no-op in every PR mode: no PR is selected or merged. Keep that result rather than retrying publication.

Before publication mutates state or a remote, reintegration refreshes the agent's bead scope from the authoritative bead_store_reconciled assignment surface. A HEAD subject naming a closed, deleted, duplicate, superseded, foreign, or otherwise unassigned bead is refused before hooks, push or gh, with the canonical status and owner as evidence. Historical bead IDs in the landing range are excluded from Beads: metadata. An explicitly empty assignment set permits a genuinely beadless operational PR, but goal text, commit bodies, summaries and docs prose never create bead associations.

If a PR-backed path reports that it published a branch but has no PR URL, the work is not verified as landed, even if the pushed branch contains the code. Preserve the agent branch, backup refs, pushed branch name, commit SHAs and command output, and file or update a reintegration-anomaly bead instead of closing the implementation bead or retrying blindly.

Product PR generations

caco pr product publication still publishes each source head as an immutable sibling head, <branch>-pr-g<source-head>, so republishing an unchanged head reuses the same PR. The published PR title keeps the primary bead ID when the subject is clamped to 72 characters, so a retry can re-derive its identity from the title.

caco pr generation-inventory is the read-only surface that names which open generations are strict-prefix predecessors of a live head:

# from the publishing agent's own checkout
caco pr generation-inventory

# from a controller checkout, for a run it does not own
caco pr generation-inventory \
  --fetch origin \
  --branch agent/<node>/<project>/<agent-id> \
  --head <live-source-head>

It never mutates a pull request, branch or label. Ancestry is proved from local history, comparing source heads when both sides have them and published tips otherwise; when no basis is shared the row is unproved and names what is missing. Use this output as the evidence for any manual closure; publication itself never closes an older PR.

Direct/local-merge fallback

When the project policy says default_intent: direct and backend: local_merge, caco agent reintegrate --mode direct lands on the configured integration.reintegrate_target directly and does not create a PR. caco agent reintegrate --backend local_merge is the per-call escape hatch off a broken PR backend. If a worker on that topology receives PR-backend or no-URL wording, check for node binary or config skew before assuming the current source path is broken.

GitHub CLI routing

PR operations invoke gh through the project's configured host and account; see explicit project GitHub routing. Managed sessions have no gh PATH shim, so scripts and agents use caco pr or scripts/pinned-gh.sh --project <project> -- <args>, never bare gh.

Agents do not run gh pr create themselves to land their branch. PR creation is owned by the lifecycle surfaces (caco agent reintegrate, caco agent complete and the manual caco pr product tool), which publish the agent PR branch to the project's configured forge remote and open the PR there rather than against the daemon-local mirror origin.

land-pr is status-only unless critical

Landing belongs to GitHub auto-merge and the controller, not to an author-agent watch loop. caco agent land-pr --id <agent> does one gh pr view, reports the PR state with a no-babysit advisory, and returns; it does not sleep, update the branch, or fall back. The bounded client-side update and CI watch runs only with explicit --critical or CACO_LAND_PR_CLIENT_WATCH=1, for a land an operator or controller classifies as critical. Exhausting that watch preserves the PR and checkout and never changes the integration mode or starts another publication. The legacy --auto-fallback flag reports legacy_auto_fallback_refused rather than attempting a direct merge. Explicit PR close remains a separate canonical operation, never an exhaustion side effect.