Plan Pipeline β End-to-End Runbook
You write a spec. Claude writes the code across every repo the work touches. You review the PRs as they appear and merge them when you're happy.
This page covers the entire flow from "I have an idea" to "all the work PRs are merged" β what you do, what the pipeline does for you, and every Slack DM you'll receive along the way.
The whole flow at a glance
flowchart LR
Idea[Idea] --> Ask["Ask agent to write plan"]
Ask --> PR["Plan PR opens<br/>(plan-and-design-repo)"]
PR -->|auto| Stamp["Auto: Linear epic created,<br/>linearIssueId stamped,<br/>cost/ROI stamped,<br/>plan-review-agent verdict"]
Stamp --> You["You: iterate, get human approval, merge"]
You -->|auto| Dispatch["Auto: fanout dispatches<br/>per (repo Γ planItem)"]
Dispatch --> Wave1["Wave 1: PRs open<br/>in target repos"]
Wave1 --> Review["You: review + merge each PR"]
Review -->|deps unblocked auto| Wave2["Wave 2: deferred PRs<br/>open as upstream merges"]
Wave2 --> Done["Linear epic auto-moves<br/>to Done"]
Step 1 β Write the plan
The simplest path is to ask Cursor, Claude Code, or Codex to write it for you. The agentic-planning skill is mandatory on every agent surface β it loads automatically when you ask any agent to "plan", "spec", or "ticket" anything.1
Example prompt
"Plan: add a pricing-cohorts admin endpoint and admin UI in cred-platform-ts. The endpoint stores cohort rows with id/name/ruleType, the UI is an
/admin/pricing-cohortspage that lists them and has a create modal. UI depends on the API existing first. Two planItems."
The agent will ask 1β3 clarifying questions, then write the plan file to plans/<slug>/v1.md in plan-and-design-repo with every required field filled in.
What's manual vs. automatic in the plan file
| Field | Source | Notes |
|---|---|---|
id, title, targetRepos |
You (or the agent on your behalf) | targetRepos is the only real architectural decision |
planItems[].summary, acceptanceCriteria, files |
You / agent | This is the spec β be specific, name file paths |
phases, planItems[].dependsOn |
You / agent | Only needed if you have ordering between items |
budgetUsd, costRationale |
You / agent | Usually 2β5; over $5 needs a budgetException block |
ownerUserId |
You β hand-write user_<your-github-handle> |
Currently manual. Not auto-stamped from PR author (yet) |
templateVersion: 5 |
You β constant | Schema rejects anything else |
status: draft |
You β constant on open | Don't flip to approved; merge IS approval |
parentLinearKey: "new" |
You β optional | Set to "new" for a root-level Linear epic; omit to let the bridge auto-resolve a parent |
linearIssueId |
Auto-stamped | cred-planner-agent[bot] writes it on PR open |
costEstimate, roiEstimate, valueRatio |
Auto-stamped | Via plan-pr-value-stamp.yml on every push |
The full schema lives at plan-and-design-repo/schemas/plan.schema.json.
Step 2 β Open the PR
git checkout -b <your-handle>/<slug>
git add plans/<slug>/v1.md
git commit -m "plan(<slug>): one-line description"
git push -u origin <your-handle>/<slug>
gh pr create --base main --fill
Local npm run validate may complain about missing linearIssueId
That's expected before the auto-stamp commit lands. Push first; the stamp arrives ~30 seconds later.
Step 3 β Watch the bots do their work (~2 min)
Bots will hit your PR in quick succession. You don't need to do anything yet β just read what shows up.
| Bot / Workflow | What it does | Source |
|---|---|---|
cred-planner-agent[bot] auto-stamps linearIssueId |
Calls linear-bridge β creates Linear epic (root-level if parentLinearKey: new, else auto-resolves a parent) β commits the COM-XXXXX key back to your branch |
plan-pr-opened.yml |
| Plan PR value-stamp | Calls value-bridge/estimate β writes costEstimate, roiEstimate, valueRatio into frontmatter. Fingerprint-gated β only re-stamps when plan body bytes change |
plan-pr-value-stamp.yml |
cred-plan-review-agent[bot] posts verdict |
APPROVE / REQUEST_CHANGES / ESCALATE to human β read the line comments |
plan-review.yml |
cred-design-review-agent[bot] (UI plans only) |
Reviews co-located design files at designs/<slug>/v1.md |
design-review.yml |
Backend-only plans (target repos in config/repo-policy.yml#nonUiRepos) don't need a design β design-review skips silently.
Pull before you push more commits
The auto-stamp commit moved your branch forward. Run git pull --ff-only before adding fixes.
Step 4 β Iterate with plan-review-agent
The agent's verdict comes with concrete asks. Common ones:
- Tighten a vague
summaryβ Claude quotes summaries verbatim into its impl prompt, so vague spec β vague code later - Add missing
files:entries β fanout refuses to dispatch a slot with zero files - Raise
budgetUsdβ if the scope is bigger than the budget allows - Split a too-large planItem β better isolation, easier review
- Add
dependsOnβ if you missed an ordering constraint
To iterate: edit v1.md, commit, push. The bots re-review on the new commit automatically. Usually 1β2 rounds.
Step 5 β Merge
Branch protection requires one human-authored APPROVED review (bots' approvals don't count toward the gate). Once that's in and CI is green:
Squash-merge the PR.
You don't have to flip status: draft β status: approved in frontmatter. The "merge IS approval" doctrine2 treats the merge itself as the signal β branch protection + plan-review-agent + human review together define "approved" for the pipeline.
After merge, you're done with plan-and-design-repo. Now wait for work PRs.
Want to skip the fanout?
Add the autobuild-excluded label to the plan PR before merging. post-merge-dispatch will skip the repository_dispatch entirely. Useful for plans that exist purely as documentation or for plans that supersede an earlier version.
Step 6 β Fanout PRs appear (timing varies)
Within ~10β15 minutes of plan merge, work PRs start opening in your target repos. The pattern depends on your plan structure.
Multi-repo plans open in waves β you have to merge the upstream PRs before the downstream ones appear
If your plan has cross-repo or cross-item dependsOn relationships, the fanout will NOT open all the PRs at once. It opens the upstream PRs first, waits for you to merge them, and only then opens the downstream PRs.
Example: a plan targeting cred-api-commercial (API change) + cred-web-commercial (UI consuming the API) with P2-ui dependsOn P1-api:
- T+15 min: PR opens in
cred-api-commercialonly. No PR incred-web-commercialyet. This is correct β don't panic. - You review + merge the
cred-api-commercialPR. - T+~5 min after merge: the Stale PR Sweeper picks up the merge state change.
- T+~15 min after merge: PR opens in
cred-web-commercial.
Your job during the wait: review + merge the upstream PR. Don't go hunting for the downstream PR β it's intentionally deferred. Linear sub-issue for the deferred item will carry a comment like Deferred: waiting on P1-api.
What happens under the hood
sequenceDiagram
participant Plan as plan-and-design-repo<br/>(PR merged)
participant Dispatch as post-merge-dispatch.yml
participant Bridge as cred-agent-ai<br/>"Author Agent plan-merged bridge"
participant Fanout as author-agent-plan-merged.yml<br/>(reusable, workflow_call only)
participant Targets as Target repos
Plan->>Dispatch: pull_request.closed (merged)
Dispatch->>Bridge: repository_dispatch:<br/>author-agent-plan-merged
Bridge->>Fanout: workflow_call
Note over Fanout: plan-context job:<br/>parse plan, build matrix
Note over Fanout: target-repo-preflight:<br/>check AGENTS.md, app install,<br/>default branch per slot
Note over Fanout: sync-linear-tickets:<br/>create Linear sub-issues
Note over Fanout: fan-out-prs:<br/>one slot per (repo Γ item)
Fanout->>Targets: Open PRs<br/>(cred-author-agent[bot])
The bridge is intentionally thin β it just validates the dispatch payload and calls the canonical workflow via workflow_call. The canonical workflow no longer accepts direct repository_dispatch (a poisoned-record incident, COM-40223).
Pattern A β Simple plan (no dependsOn, no phases)
All planItems fan out in parallel. Three planItems β three PRs at roughly the same time. You don't need to merge them in any particular order.
Pattern B β Plan with cross-item dependsOn (single repo)
Items with no upstream deps open immediately. Items with deps wait β they don't get a PR until their upstream PR is merged.
T+0 you merge the plan PR
T+15 β
PR for P1-api opens in cred-platform-ts (no deps)
βΈ P2-ui (dependsOn: [P1-api]) β NO PR YET, intentionally deferred
ββ Linear sub-issue for P2 says: "Deferred: waiting on P1-api"
π YOU: review + merge the P1-api PR like a normal code review
T+X you merge the P1-api PR
T+X+5 sweeper detects the merge
T+X+15 β
PR for P2-ui opens in cred-platform-ts
ββ Linear sub-issue for P2 transitions out of "Deferred" state
π YOU: review + merge the P2-ui PR
You don't trigger the second wave. The Cred Stale PR Sweeper runs every 5 minutes (schedule: */5 * * * *) and also on relevant repository_dispatch events. It picks up the merged upstream and re-dispatches deferred slots automatically.
Pattern C β Cross-repo dependencies (the multi-repo wave pattern)
Same as Pattern B, but the upstream and downstream items are in different target repos. The deferral logic looks across all targetRepos. Concrete walkthrough using the example from the callout above:
Plan:
targetRepos:
- credinvest/cred-api-commercial
- credinvest/cred-web-commercial
planItems:
- P1-api (repo: cred-api-commercial)
- P2-ui (repo: cred-web-commercial, dependsOn: [P1-api])
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
T+0 β
You merge the plan PR in plan-and-design-repo
T+15 β
cred-api-commercial: PR #N opens β upstream PR
βΈ cred-web-commercial: no PR yet β deferred
⬩ Linear: P2 sub-issue marked "Deferred: waiting on P1-api"
π YOU now: review + merge cred-api-commercial PR #N
[hours/days later β you may be iterating with the author-agent on PR #N
via `address-comments` mode, or pushing fixes yourself]
T+X β
cred-api-commercial PR #N merges
T+X+5 β± Sweeper tick (runs every 5 min) detects the merge
T+X+15 β
cred-web-commercial: PR #M opens β downstream PR
⬩ Linear: P2 sub-issue moves to "In Progress"
π YOU now: review + merge cred-web-commercial PR #M
T+Y β
cred-web-commercial PR #M merges
⬩ Linear: parent epic auto-moves to "Done" (all sub-issues done)
The key thing to internalise: "PR for one repo, nothing for the other" is the expected state, not a stuck pipeline. The downstream repo is waiting on you to merge upstream. The sweeper will reliably auto-open the downstream PR within ~15 min of you merging upstream.
If you have THREE repos chained (A β B β C), you get three waves: one PR in A, you merge, one PR in B, you merge, one PR in C, you merge. Each wave is ~15 min after the previous merge.
Pattern D β Multi-phase
If you used phases with gateCriteria: phase-1 items fan out first. Phase-2 items wait until ALL phase-1 PRs merge AND gateCriteria is satisfied. Same wave behavior as Pattern C, but the unit is "phase" instead of "individual planItem".
phase-1: items P1, P2, P3 β 3 PRs open at T+15
(in parallel β no deps inside the phase)
[you merge all 3 phase-1 PRs]
phase-2: items P4, P5 β 2 PRs open ~15 min after the LAST
phase-1 PR merges
How to tell which pattern you're in
Look at your plan file:
| If your plan has⦠| You're in pattern⦠| Expected PR behavior |
|---|---|---|
One targetRepo, one planItem |
A | Single PR opens immediately |
One targetRepo, multiple planItems, no dependsOn between them |
A | All N PRs open at once in the same repo |
Any dependsOn between items in the same repo |
B | Upstream PR first; downstream PR after upstream merges |
Multiple targetRepos with dependsOn crossing repos |
C | One repo's PR opens first; other repos' PRs open as you merge |
phases: with multiple phases, items split across them |
D | Phase-by-phase waves |
Step 7 β Reviewing the work PRs
Each PR shows up in your target repo, authored by cred-author-agent[bot], formatted as:
feat/fix(<repo>): <planItem-id> β <summary first 80 chars>β¦ (COM-XXXXX)
The branch is cred-author-agent/<your-slug>-v1-<planItem-id>. PR body links the Linear sub-issue + the parent plan. Same branch protection, same CI gates as a human PR.
How to review
| Situation | Action |
|---|---|
| PR looks correct, CI green | Merge it. Same as any human PR. |
| PR has a bug or wrong approach | Drop a comment: "@cred-author-agent the cohort filter should also accept null ruleType." The author-agent's address-comments mode re-runs Claude with your feedback and pushes a fix. |
| CI is failing | address-comments mode usually auto-fixes. If stuck after 2 retries, push a fix yourself. |
| The whole approach is wrong | Comment with reasoning. If the spec itself is wrong, open v2.md of the plan with supersedes: 1, fix it, merge. v1 in-flight PRs are superseded. |
PR opened in [DRAFT] with almost no diff |
Scaffold-only PR β Claude couldn't act on the spec. See "Scaffold-only PR" Slack DM below. |
Author-agent modes
address-commentsβ runs when a human comments on the PR, when CI posts a failure, or via merge-agent dispatchrebaseβ when the base branch has movedrecoveryβ manual fallback when an earlier run died mid-flight
Linear automation
Everything Linear-side is automatic. You don't open Linear tickets manually for plan-driven work.
| Linear event | Who handles it | When |
|---|---|---|
| Parent epic created | linear-bridge via plan-pr-opened.yml |
On plan PR open |
Epic In Review β In Progress |
post-plan-merge-linear.yml (COM-36916) | On plan PR merge |
| Sub-issues created per planItem | sync-linear-tickets job in author-agent-plan-merged.yml |
During fanout |
| Sub-issue state synced with PR state | Native Linear β GitHub integration (primary) + cred-linear-sync.yml every 4h (gap-filler for squash-merge edge cases) | Continuous |
| Blocker auto-unblocked on completion | cred-linear-sync.yml blocker-watcher mode every 4h |
Continuous |
Parent epic Done |
Linear's native sub-issue rollup when all sub-issues are Done |
Automatic |
If a sub-issue's state ever drifts from the PR state β usually because squash-merge strips the COM-XXXXX reference from the merge commit β the 4-hour cred-linear-sync.yml job reconciles it. You don't need to touch Linear.
Slack notifications β what you'll receive
All DMs route to you personally via the slackUserId mapped to your ownerUserId in cred-agent-ai/.github/data/plan-author-slack-ids.json. If you're not in that file, the DM falls back to a channel-wide webhook post.
Add yourself to the registry
If you haven't, open a PR adding an entry to .github/data/plan-author-slack-ids.json with your name, GitHub login (lowercase), and Slack user ID. Otherwise every plan you author pings the whole channel instead of just you.
DMs you'll see
| When | Emoji | Message |
|---|---|---|
| Plan PR opened & reviewed | :white_check_mark: Plan review approved |
"Plan is approved. After CI green + human merge, the post-merge dispatch fires fan-out to your target repos." |
| Plan PR opened & reviewed | :no_entry: Plan review requested changes |
"Open the PR review for the bot-author findings + citations. Address them and push a new commit β plan-review-agent will re-evaluate." |
| Plan PR opened & reviewed | :raising_hand: Plan review escalated to human |
"The agent could not produce a confident verdict and asked a human to look. Tag a reviewer per CODEOWNERS." |
| UI plans only β design review verdict | :white_check_mark: / :no_entry: |
Mirrors plan-review tone, but for the co-located design file |
Plan PR merged with autobuild-excluded |
(notice) | "PR #N merged with the 'autobuild-excluded' label β no repository_dispatch fired." So you know fanout was intentionally skipped. |
| Fanout dispatched | :rocket: Plan fan-out dispatched |
"cred-agent-ai will now open one PR per target repo (N repos). Expect Slack updates as each fan-out slot opens / refreshes / fails." |
| Cross-repo dependency deferral | :hourglass_flowing_sand: Plan fan-out deferred β cross-repo dependency |
Lists which planItems are deferred and why. Next: wait for the upstream PR(s) to merge; the sweeper auto-re-dispatches when they do. |
| Plan-context job failed | :octagonal_sign: Plan fan-out aborted β plan-context job failed |
Cause + next steps + run link. No PRs were opened. |
| Scaffold-only PR opened | :construction: Plan fan-out produced a scaffold-only PR |
Claude wrote stubs but no real implementation. PR is labeled needs-implementation β Merge Agent gate blocks merge until implementation lands. Usually means the planItem spec was too vague; open v2.md with sharper instructions. |
| Stuck draft PR nudge (periodic) | (varies) | Sent by cred-stuck-fanout-pr-nudge.yml β periodically pings you about draft fanout PRs that haven't progressed in a while |
| Daily digest (channel-wide, not a DM) | β | Cost / Tokens / Speed / Quality / Regressions / Interventions rollup posted to the agent ops channel |
Every DM falls back to a channel-wide webhook post if the bot DM fails (no Slack mapping, conversations.open error, etc.) β you'll never silently lose a notification.
When things look stuck
| Symptom | First thing to check |
|---|---|
| Plan merged 20 min ago, no PRs anywhere | Open the cred-agent-ai Actions tab β Author Agent plan-merged bridge run for your slug. The link-back step summary explains why (deferred / blocked / preflight failure / matrix empty). |
| PR opened in one repo, the others didn't | This is not stuck β this is the multi-repo wave pattern. The missing PRs are deferred because they dependsOn the open one. Merge the open PR; the deferred ones auto-open ~15 min after. See Pattern C. Confirm by checking the Linear sub-issue β it'll say Deferred: waiting on <item-id>. |
| Fanout PR keeps failing CI and author-agent isn't fixing it | The acceptance criteria was too vague, or the change is fundamentally wrong. Push a fix yourself, or comment with explicit guidance, or close + open v2.md. |
Fanout PR says [DRAFT] with scaffold-only diff |
summary / acceptanceCriteria was too vague. Open v2.md of the plan, tighten the spec, merge β the v1 PR gets superseded. |
| Slack pinged the wrong channel about your plan | You're not in plan-author-slack-ids.json. Open a PR adding yourself. |
Plan PR merged but no Post-merge dispatch run |
Open the plan-and-design-repo Actions tab β there's a Post-merge dispatch (plan-merged) workflow per merged plan PR. If it's failed, the dispatch token may be expired. Ping the platform on-call. |
TL;DR β the daily flow
- Ask Cursor/Claude Code: "Plan this work for me." It writes the plan and opens the PR.
- Skim the planItems' summaries and acceptanceCriteria for the 3 things that matter: file paths named, contract clear, target repos right.
- Iterate with the plan-review-agent's verdict. 1β2 rounds usually.
- Get one human approval and merge. No status flip needed.
- Go do other work. Slack will DM you as fanout PRs open, get deferred, succeed, or need attention.
- Review and merge the PRs that appear in your target repos. Drop comments to redirect the author-agent if needed. If your plan has cross-repo deps, you'll get PRs in waves β merge the upstream repo's PR first, then the next wave opens ~15 min later.
- Done. Linear closes itself.
Don'ts
- β Don't open work PRs in target repos yourself β let the pipeline open them
- β Don't fire
repository_dispatchmanually - β Don't flip
status: approvedin frontmatter before merge β merge is the gate - β Don't create Linear tickets for plan work β they're auto-created
- β Don't push code commits to the plan PR β only the plan file should change there
- β Don't skip the agentic-planning skill β it's mandatory on every agent surface
References
- Plan template:
plan-and-design-repo/templates/plan-template.md - Plan schema (every required field):
plan-and-design-repo/schemas/plan.schema.json - Repos that skip design (
nonUiRepos):plan-and-design-repo/config/repo-policy.yml - Slack alias registry (add yourself here):
cred-agent-ai/.github/data/plan-author-slack-ids.json - Canonical fanout workflow:
cred-agent-ai/.github/workflows/author-agent-plan-merged.yml - Live fanout dashboard: https://github.com/credinvest/cred-agent-ai/actions/workflows/author-agent-plan-merged.yml
- Mandatory agent skill:
cred-agent-ai/.agents/skills/agentic-planning/SKILL.md
-
Enforced in cred-agent-ai/AGENTS.md β "Mandatory skills (load before any planning)". The canonical skill file lives at
cred-agent-ai/.agents/skills/agentic-planning/SKILL.md. ↩ -
Documented in post-merge-dispatch.yml header (COM-36627). ↩