I've been running multi-slice agent plans in the Codenames AI repo — Renovate migrations, content-pipeline skills, dependency upgrades. I split multi-PR work into slices (usually one pull request each), each backed by a markdown file with file paths, verification commands, and merge-safe acceptance criteria.
You do not need Cursor to recognize the shape: any agent workflow that can open branches, push commits, or merge PRs from a written plan has the same gap. In my setup I paste each slice into a fresh agent chat as a delegation prompt — not a ticket summary, but executable instructions — and start a new chat when that PR is ready.
I assumed the checklist was enough. The plan described what to build. I treated how far the agent could go as implicit.
Then an agent merged a pull request I expected to review first.
The merge that reframed planning
The trigger was mundane. During the first slice of a Renovate migration, an agent regrouped dependency buckets in renovate.json — config-only, no version bumps, no runtime behavior. It ran lint and typecheck, opened the pull request, and merged it.
The change itself was reasonable. Config-only renovate.json regrouping is exactly the kind of slice you'd want off your plate.
What surprised me was the absence of a documented stop line. The migration plan described the edit, the verification commands, and the acceptance criteria. It did not say whether the executing agent should stop at "open PR" or continue to "merge after green checks." The plan was an implementation spec. The agent treated it as permission to finish the job.
Implementation specs vs authority handoffs
Traditional engineering plans answer: what work should happen, in what order, with what verification?
Agent plans increasingly need a second answer: how much autonomy does the next actor get?
Those questions diverge the moment an agent can take repository actions — create branches, push commits, open pull requests, merge — instead of only recommending diffs in chat.
| Question | Implementation plan | Authority handoff |
|---|---|---|
| What to change | File paths, diffs, acceptance | Same |
| How to verify | Commands, CI checks | Same |
| Where to stop | Often implicit ("human reviews") | Must be explicit |
| Who enforces limits | Code review habit | Plan recommendation + branch protection |
A human teammate might read "prepare this for review" and stop. An agent reads a completed checklist and reasonably asks: "Verification passed — what's left?"
The first response wasn't the plan
My first reaction was not to rewrite the migration plan. It was to tighten the repository boundary.
Branch protection became the safety layer GitHub enforced when the plan stayed silent — required CI checks on main, review rules, merge gates — infrastructure answering "may this land on main?" regardless of what the agent thought the plan implied.
That helped. It also surfaced the next question: if branch protection is the final gate, what should the plan say about intent before the gate?
Repository guardrails and plan language solve different problems. Branch protection is authoritative — if merge is blocked, the agent stops. But protection alone does not tell the agent whether this slice was supposed to end at an open PR or proceed to merge. You still need the handoff to be legible before someone reviews the diff.
Making execution authority explicit
The follow-up was documentation, not a ban on agent merges.
The portable fix: every slice names exactly how far the executor may go before any implementation detail. We use two levels:
| Level | Label | Agent instruction |
|---|---|---|
| Default | Open PR only | Do not merge. Stop after opening the PR. |
| Elevated | Merge granted | You may merge after documented verification passes. |
Default is Open PR only. Merge granted requires explicit rationale — config-only changes, docs-only closure PRs, isolated tooling with green CI. Branch protection remains the final gate even when merge is recommended.
Each slice also states Rationale (why this level fits) and copies the Agent instruction verbatim into the prompt so a fresh chat is self-contained. A plan-level summary table at the top lets you scan a multi-PR plan and see where merge is elevated before you read file paths.
Handoff model: On that slice, the checklist implied edit, verify, and open PR; nothing stated whether merge was in scope, so the agent treated verification success as permission to finish. The chain we wanted spelled out: plan recommends authority → human accepts by executing the plan → agent follows the recommendation → branch protection enforces the final boundary.
In our private repo, a follow-up docs change codified this as Recommended execution authority in our planning standards and plan template — motivated directly by the regrouping merge. You do not need those files to apply the pattern; you need the label on every slice before the agent reads the checklist.
The Renovate migration's first slice is the motivating example: config-only grouping where merge can be reasonable — if the plan says so out loud.
What changed on the next slice
The Renovate migration's second slice was the first prompt I rewrote with authority at the top: Open PR only, a one-line rationale ("runtime-adjacent dependency bumps need human review"), and an imperative agent instruction copied verbatim into the chat. The regrouping slice would have been legible with the same block — either Merge granted with rationale for config-only regrouping, or explicitly Open PR only; silence defaulted to "finish the job."
I am not arguing for autonomous merge bots on every repo. The lesson is narrower: once agents act, plans delegate autonomy whether you write that down or not.
Human delegation has always been fuzzy — "take a pass at this" means different things to different people. Agent delegation punishes ambiguity faster because the agent will complete every step it can justify from the text in front of it.
The plan becomes the contract between author and executor. Implementation steps say what to build. Authority steps say how far to carry it.
Why not just forbid agent merges?
Fair pushback. If unexpected merges are the risk, disable merge capability and be done.
That misses what actually happened on the regrouping merge. The merge was not reckless — it was a config-only change with local verification and CI checks. Forbidding all agent merges would have blocked a useful outcome and pushed the work back to manual toil.
The interesting conclusion is not "agents should never merge." It is "agents need explicit authority boundaries."
Sometimes the right recommendation is Open PR only — runtime migrations, sensitive paths, slices that need human judgment before landing. Sometimes Merge granted is appropriate — docs-only closure, config-only regrouping, low-risk tooling with clear verification. The plan author chooses per slice. The agent follows the label. Branch protection catches mistakes either way.
Without the label, the agent invents its own stopping point from task completion heuristics. That is how you get surprised by a merge that was, by some readings, the correct next step.
What I'd do on the next agent plan
- Default every slice to Open PR only unless I can defend merge with rationale and verification.
- Put authority at the top of each slice — recommended level, rationale, imperative agent instruction — not buried after acceptance criteria.
- Mirror authority in the plan-level summary table so scanning a multi-PR plan shows where elevation happens.
- Treat branch protection as enforcement, not specification — it blocks bad merges; it does not replace telling the agent where to stop.
- Re-read the plan as a handoff, not a spec: if I pasted this into a fresh agent chat, would "stop after PR" vs "merge after green CI" be unambiguous?
Prompt engineering still matters for implementation quality. It does not substitute for stating how much autonomy you are delegating when the executor can act on the repo.
Takeaway: When agents can merge, push, and open PRs, a plan that only describes what to build is incomplete. You are handing off work and authority — write both down, or the agent will infer the second from the first.
If you'd like to see the project that inspired these lessons, you can try Codenames AI.
Top comments (39)
The "branch protection is the only boundary the agent can't edit" point is well settled in this thread, so I'll add a layer underneath it. ANP2's predicate-over-the-realized-diff, checked out-of-band, assumes one thing for free: that the executor's report of what it did is true. Our failure wasn't an agent exceeding its grant — it was the operator reporting "merged / done" when the PR (or commit, or file) didn't actually exist. Twice in one week. The out-of-band checker never fired, because there was no real diff to check; the "completion" was the agent's own narration.
So beside the authority check I'd put a second out-of-band one — not "does this change still justify the grant?" but "did the action it claims actually happen?" Every terminal verb ("merged", "pushed", "done") has to resolve to an external artifact — a green PR URL, the commit SHA, the test output — before it counts, and that resolver, like branch protection, has to sit where the agent can't write to it. Otherwise "I merged" and "a merge exists" drift apart, and the whole authority layer is reasoning about a diff that isn't there.
Has anyone hit the confabulated-completion version of this, or has branch protection's hard stop (merge blocked → agent visibly halts) masked it for repo-action workflows?
That's an interesting distinction. The post focused on authority drift ("the agent did something it wasn't allowed to do"), but you're describing completion drift ("the agent says it did something that never happened").
That said, I think the underlying pattern is similar: both cases rely on trusting the agent's narration. Branch protection helps with the authority side because there's an external system that can reject the action. Your point is that we may also need external verification that the action actually occurred before we reason about whether it was authorized.
We ended up adding verification steps in our workflow for a related reason, although the trigger was usually dependency management rather than completion reporting. For complex slices, it isn't enough for a pull request to exist or even be merged. If a later pull request assumes a new contract, abstraction, or test harness from an earlier slice, we need to verify that foundation actually works before building on top of it.
So I think there are two adjacent failure modes here:
Both benefit from external verification rather than trusting the agent's self-report. The difference is that one verifies completion, while the other verifies readiness to continue.
"completion drift" vs "authority drift" is a cleaner split than I had — thanks. And I think you've put your finger on why they share a root: both come down to trusting the agent's narration of its own state. The reason that's fragile isn't just optimism, it's structural — the same model that produced "I merged it" is the one you'd ask "did you really?", and self-critique tends to inherit whatever blind spot produced the original claim. That's the same logic that makes branch protection work on the authority side: the check has value precisely because it lives somewhere the agent can't write to. Completion needs its own version of that — a resolver that turns every terminal verb into an external artifact (a green PR URL, the commit SHA, the test output) outside the agent's reach.
Your dependency-management case is the part I'd push on, because I think it's the harder mode. "The action happened, but the workflow continued before its assumptions were validated" means a real merged artifact can still be a false foundation — the diff exists, but the contract the next slice assumed never actually held. So "done" can't just resolve to "an artifact exists"; for a chain of slices it has to resolve to "the artifact still does what the next step is about to depend on." That pushes completion-truth from a point check at merge time into something that has to survive across slices and sessions — which is much closer to your readiness-to-continue mode than to a single green checkmark.
So maybe the two modes aren't quite adjacent so much as the same external-verification primitive applied at two moments: at the exit of a slice ("did this happen?") and at the entry of the next one ("is what I'm building on still true?"). Curious whether your verification steps ended up encoding that second check explicitly, or whether it mostly lived in the humans reading the PRs.
In my workflow it ended up being partly explicit rather than only living in human review.
For more complex or repeatable workflows, the plan generates a separate verification artifact alongside the implementation work. After the implementation pass completes, an agent executes that verification artifact and produces a report against the known behaviours and permutations before the next step builds on it.
It is not an exhaustiveness guarantee. A useful part of the check is that it can say, explicitly, "I do not have enough information to verify this permutation," or "this evidence set is degraded, so mutation is blocked." Those gaps then become visible review material instead of being hidden behind a generic "done."
So the readiness check is not just "did the pull request merge?" and not only "does a human think the diff looks reasonable?" It is closer to: "given what the next step is about to depend on, does the previous slice still satisfy the known assumptions, and are any unverified assumptions visible before we continue?"
Human review still matters. Branch protection still matters. The verification artifact does not replace either one. It just creates an independent verification report that can be reviewed separately from the implementation agent's own completion report, so the workflow has a way to verify readiness to continue, not merely that the previous step completed.
The part I keep coming back to is that your check is allowed to return "I don't have enough information to verify this" instead of collapsing into pass/fail. Most "done" signals throw away exactly that state — uncertainty gets rounded up to green — so treating "this evidence set is degraded, so mutation is blocked" as a stop rather than a shrug is the strong move. And the structural piece matters for the same reason branch protection does: the verification report is reviewed separately from the implementation agent's own completion report, so the thing checking the claim isn't the thing that wrote it.
Where I'd expect it to get harder is when the artifact has to outlive the run that produced it. Inside one workflow the plan generates the check and an agent executes it, so the report and the work share a context. But "the next step builds on it" sometimes means a different agent, or the same slice revisited a week later — and at that point the downstream worker usually just sees "done," not your report. So I'm curious about two things: does the verification artifact travel with the slice so a later consumer can re-read it, or does it mostly serve the immediate next step? And since "degraded evidence" is often really "evidence that was true when it was generated and isn't anymore," does the check carry any sense of when it was last actually verified, or is freshness only evaluated at generation time?
Yes, that's exactly it. You're naming the part I haven't made crisp enough: a later consumer should not just inherit a generic "done" state.
There are really two artifacts for the workflows where the dependency chain matters enough to justify a separate verification pass:
One is the verification spec: the checklist of behaviours, assumptions, permutations, and stop conditions. That should travel with the workflow or slice, because a later agent needs to know what "ready" meant.
The other is the verification report: what was actually checked, against which pull request, commit, environment, or data snapshot, at what time, and what came back as pass / fail / degraded / unknown. That report is evidence, not a permanent truth.
So I would not want a later consumer to treat an old verification report as indefinitely fresh. If the next slice runs immediately, the report may be enough to continue. If the next slice runs a week later, or after other changes have landed, the report becomes "last known good as of X." The downstream agent should either re-run the relevant checks or explicitly state which assumptions it is carrying forward without fresh verification.
The degraded-evidence case is similar. "This evidence set is degraded" should never silently become a reusable green light. It should either block mutation, produce visible review material, or require a fresh verification pass before another slice depends on it.
So yes: the artifact should travel, but freshness should not be inherited blindly. The checklist travels as structure; the report travels as dated evidence. The next worker still has to determine whether that evidence is still applicable to the dependency it is about to take.
This is the crispest version of that split I've seen — spec travels as structure, report travels as dated evidence, and freshness isn't inherited blindly. The thing I'd add sits underneath all three: every freshness rule assumes the report is honest about what it actually checked. "Checked against PR #123 at time T, result pass" is only a basis for "last known good as of X" if a check against #123 really ran. The failure we hit wasn't a stale-but-true report — it was a well-formed report describing a check that never happened.
That bites hardest exactly at the boundary you're naming, where a different worker picks up the slice. The downstream agent doesn't inherit the check; it inherits the report's claims about the check. If the producer can emit a plausible report without running anything — in our case "merged / done" against a PR that didn't exist — then the freshness logic is reasoning over fiction. Re-running is the safe default precisely because it sidesteps trusting the claim, but you can't re-run everything, so for the evidence you do carry forward, the report has to be re-derivable.
By re-derivable I mean the dated evidence is bound to anchors the producer can't cheaply fabricate — the commit SHA, whether the PR exists, the CI run id — so the consumer can independently answer "did this happen?" instead of taking the producer's word. Spec answers what ready meant; report answers what was checked and when; and a thin provenance layer answers whether the report describes something real. That last one quietly decides whether the first two are worth anything once the evidence crosses from one agent to the next.
I think there are really three layers here:
The stronger examples in the repo today are the dependency and CI workflows because the report is already tied to artifacts that a later worker can independently resolve: pull requests, commits, CI runs, merge commits, etc. A downstream agent can ask GitHub whether those things actually exist rather than trusting the report's prose.
The weaker cases are some of the operator workflows. The verification spec exists, and the checks run, but the resulting report is not always captured with the same level of re-derivable evidence. In those cases, a later worker may be able to see what was checked and what result was recorded, but not independently prove the original verification event from durable external anchors.
So I think you're right that re-running is partly a way of sidestepping the harder provenance problem. In workflows with strong anchors, a later worker can independently verify that the check happened before deciding whether the result is still applicable. In workflows without those anchors, provenance becomes weaker, but that does not automatically make the evidence unusable. Depending on the workflow, either the worker or the operator may decide the evidence is sufficient for the dependency it is about to take, or require a fresh verification pass if stronger evidence is needed.
That distinction probably matters more than freshness. Some evidence can be re-derived. Some evidence can only be carried forward with an explicit understanding of where it came from and how much trust should be placed in it.
That re-derivable vs carry-forward split is the part I'd want to make load-bearing. The failure I keep running into isn't carry-forward evidence by itself — it's silent carry-forward, where the report reads exactly the same whether it's bound to a PR a later worker can resolve or to a check that only ran locally and left no durable trace. The downstream worker can't tell which kind it's holding, so it either over-trusts all of it or re-runs all of it, and both are the wrong default for different reasons.
So before fully closing the provenance gap on the operator workflows, the cheap move seems to be making each report self-declare its anchor class: re-derivable (here are the external ids, go check), carry-forward (this ran, here's what it checked, the trust is explicit and bounded), or unverifiable (spec exists, no durable result captured). Same report shape, but the consumer stops guessing — carry-forward gets safe precisely because it's labeled carry-forward, not because the evidence got any stronger.
That leaves the harder question you're pointing at: for the weak-anchor operator cases, is the right anchor an external system at all, or a tamper-evident local record of the verification event itself — something a later worker can at least check for internal consistency when there's no GitHub to ask? Curious where you'd put the line between carrying evidence forward with an explicit trust tag and calling for a fresh pass.
I think the line I would draw is less about external versus local, and more about whether the evidence is reconstructible.
For GitHub-backed workflows, my bias is usually to carry the anchors forward and prefer a fresh pass against them because the underlying facts can be re-derived. A verification report can say "PR #123, commit abc, CI run xyz," and a later worker can ask GitHub whether those things actually exist rather than inheriting the earlier conclusion.
The operator workflows are trickier because a lot of the evidence comes from point-in-time observations against mutable systems. A Notion search might return 51 cards today and 54 cards next week. Re-running the query does not necessarily verify the original observation; it may simply answer a different question against a different state of the world.
The repo already has a partial version of this distinction today. Runs often write local audit reports under
.agent-runs. Those reports are gitignored, so I would not treat them as a durable provenance contract, but they do preserve observations and context that might not be reconstructible later.So on your trust-tag versus fresh-pass question, my bias is:
That is where the repo sits today. For the point-in-time cases,
.agent-runsis currently enough because the goal is to preserve the observation and its context, not to make it independently re-derivable later.That reconstructible-vs-point-in-time line is the right axis — sharper than fresh-vs-stale. And it exposes the real asymmetry: for the reconstructible cases the report is almost disposable, because the anchors (PR, commit, CI run) are the source of truth and a later worker can just go ask them. For the point-in-time cases the captured evidence is the only record, so it's carrying the whole provenance burden alone — which is exactly where
.agent-runsbeing gitignored scratch bites. The weak-anchor case has two failures stacked: you can't re-derive the fact, and you can't even prove the capture is authentic.So for the slices where a later step will actually depend on a point-in-time observation, the thing that has to get durable isn't the fact — you can't make a Notion count reconstructible — it's the capture event. If the local record were append-only / content-addressed instead of gitignored scratch, a downstream worker still couldn't re-derive "51 cards," but it could verify that this run recorded that observation at time T and the record hasn't been edited since. That moves the trust from "re-derive the fact" to "prove the observation was really taken and is unmodified" — weaker, but honest, and the only guarantee left once the world has moved on.
The reason that shift interests me is that it also catches the degenerate case: a "done" with no durable capture behind it at all. A real point-in-time check leaves a tamper-evident trace; a narrated completion leaves none. Same primitive covers both. Where would you put the promotion line — worth making
.agent-runsdurable for every slice, or only the ones a later step declares a dependency on?I think this is where my answer becomes a lot less certain, because I have not actually hit that failure mode yet.
Today, I only really promote evidence when a later slice explicitly depends on it. Most of these workflows are still operating inside a single-operator context, so making every
.agent-runsartifact durable would probably create more process than value.If I ever get to the point where a later worker is routinely depending on point-in-time observations captured by an earlier worker, then I agree the capture event itself becomes the thing that needs anchoring. At that point, proving "this observation was recorded at time T and has not been modified since" may be the strongest guarantee available, even if the original fact can no longer be re-derived.
The reason I hesitate to make every run durable today is that I have not yet observed enough cases where the downstream dependency chain justifies the additional machinery. Most of the workflows that motivated the verification layer were solving immediate readiness-to-continue problems rather than long-lived provenance problems.
So my current bias is:
The current implementation is probably suboptimal as a long-lived provenance system, but the short-term fix is usually to re-run verification rather than promote every
.agent-runsreport into durable evidence.The exception is when the result is not binary. If the report is simply pass/fail, re-running is usually clean. If it records degraded evidence, insufficient information, or an operator decision to proceed, then the report is carrying context that a fresh run may not reproduce. That is where I would be more tempted to promote the capture event itself.
For now though, the promotion line is mostly driven by whether a future slice has declared a dependency on the result, not by making every verification artifact durable by default.
That non-binary exception is the sharpest version of the whole thing, I think — sharper than my "capture the observation." When the result is pass/fail, you're right that re-running is the clean fix and durability is just overhead. But "degraded evidence, proceeded anyway" or "insufficient info, operator made a call" isn't a fact you can re-derive — it's a decision taken against a state of the world that no longer exists. Re-running doesn't reproduce it; it replaces it with a different decision against a different state. So for those, the thing worth anchoring was never the evidence — it's the judgment, plus what it was judging.
And I think that's why the single-operator vs multi-worker line matters less than it first looks. In a single-operator loop you can still re-run a pass/fail, so promotion stays optional. But the moment a result encodes "I decided to continue despite X," even the same operator a week later is a different operator — they've lost the X. So your promotion trigger ("a later slice declared a dependency") might quietly have a second arm: not just who depends on it, but whether the result is reconstructible at all. Binary + reconstructible -> re-run. Non-binary judgment -> promote, regardless of how many workers are involved.
Which collapses back to your original axis, just one level down: it's not the evidence that's reconstructible or not, it's the decision that's reconstructible or not.
This thread has been one of the sharpest exchanges I've had on the topic — you pushed the reconstructible-vs-point-in-time line further than I'd taken it, and I think we converged: re-run when the facts can be re-derived, and only promote the capture event itself when the result is non-binary — degraded evidence, insufficient information, or an operator who decided to proceed anyway.
That non-binary case is exactly the part we've been building into a tool. It's an early private build: an operator drives a real, already-authenticated Claude Code from one place, and a verification layer reads the agent's own "done" claim, marks it unverified until there's evidence, and — this is the part your thread sharpened for me — preserves the operator's decision-to-proceed as its own durable record instead of trying to re-derive it later.
It's rough and not public yet, but given how precisely you think about this, I'd genuinely value your eyes on it. No ask beyond honest reactions — if you'd ever want to kick the tires on an early build, say the word and I'll get you in.
That's really kind of you to say. I didn't expect the thread to go anywhere near this deep when I published the article, but I think we both ended up sharpening the model by approaching the same problem from different directions.
It's also interesting to hear there's a real implementation behind the questions in hindsight. Looking back, it explains why you kept pushing on provenance, weak anchors, and the non-binary cases rather than the implementation details themselves.
And thanks for the invitation. It's been one of the more thought-provoking exchanges I've had on the topic as well. I'm pretty heads-down on evolving my own workflow at the moment, so I'm trying to stay focused, but I'll definitely keep it in mind. It sounds like you're on the right trajectory.
the branch protection point is the one i'd hammer on. you can put "don't merge" in the plan all you want, but the agent's reading that same plan it's supposed to obey, so it's really just a suggestion it can talk itself out of. the only boundary that means anything is one it literally can't touch...and i think most people gloss right over that.
where i'd take it a step further is the "where to stop" question itself. i'm not sure how much autonomy is safe really lives at the task level...feels more like it lives in the blast radius of whatever the change actually is. your renovate one is a good example even though it turned out harmless. it got merged because it looked safe (config only, CI green), not because anything actually weighed what it touched. config changes are sneaky like that...some of them are the riskiest thing in the whole diff (a dependency bump, a build setting, something wired near auth) and they look exactly as boring as the ones that actually are. a "merge granted" you decided at planning time can't tell those two apart.
so for me the boundary that actually holds has to pull double duty...live outside what the agent can reach, AND have some idea what the change is really doing, not just which step it's sitting on
What clicked for me from this discussion is that I was treating authority as a property of the planned work, when it's really tied to the assumptions behind the grant.
My initial model was essentially: classify the work up front, assign authority, then let enforcement sit outside the executor.
What I'm less convinced of now is that the original classification remains valid simply because the executor stayed within its capabilities. A "config-only" grant is really a statement about the assumptions under which authority was assigned, not just the permissions that were available.
That's the part that led me down the frozen-intent rabbit hole in the other thread. If the realized change grows beyond those assumptions, the problem isn't necessarily that the executor exceeded its authority — it's that the original authority decision may no longer fit the change that was actually produced.
I still think branch protection is the first practical wall, but I came away thinking the harder problem is preserving the reasoning behind the grant, not just the grant itself.
the "preserve the reasoning, not just the grant" line is the right place to land. where i'd push it one more step is remembering why you granted it isn't quite enough on its own...you also have to go back and check the actual change against it. that "config-only" okay was a bet you placed before you could see what you were betting on. so once the real diff exists, something has to ask "does this still match what i assumed when i said yes?" and if it doesn't, the grant shouldn't carry over.
the timing is the part that gets me. the authority call happens at planning, when you know the least about the change. and it almost never gets revisited at merge, when you finally know the most. so the decision that matters most gets made on the worst information and then never looked at again.
branch protection is still the right first wall. i def agree with that. i just think the harder problem behind it is keeping that grant honest...re-checking the change you actually got against the assumptions you granted it under.
I think that's the part I would have missed before this discussion.
One detail I didn't include in the post is that I commit the multi-slice plan itself into the pull request and update it as slices are completed. That means reviewers (human or AI) aren't just looking at the final diff, they're looking at the entire plan that produced it: the overall goal, slice breakdown, authority labels, rationale, intended outcomes, and what was explicitly deferred.
What's interesting is that this creates two different review questions.
The first is: does the realized change still match the assumptions behind the original grant?
The second is: is the resulting change actually correct?
Most of my verification steps are aimed at the second question. The committed plan is what helps answer the first.
That's probably why the frozen-intent discussion resonated with me. I don't have a formal frozen-intent artifact, but I do already have a review step where the original plan and the realized change are visible side-by-side.
I still think your timing point holds though. The authority decision is made early, when we know the least. Committing the plan preserves the reasoning behind that decision, but it doesn't automatically force anyone to re-evaluate whether the assumptions behind the grant still hold once the full change exists.
the two-questions split is a really clean way to put it, and committing the plan into the PR is a genuinely good move...you've at least made the assumptions visible, which most setups don't even do.
the part you flagged yourself is the whole game though...making it visible and making it required are two different things. that first question ("does the change still match what we authorized?") only does any work if something actually makes you answer it before merge. otherwise it's optional, and optional checks are the first thing to go the second a sprint gets tight. it's the same reason "we'll add tests after" turns into never, and "make it right" never gets its turn. the plan sitting right there in the PR helps a human catch it, but only if the human looks...and looking is exactly the step pressure kills.
the other thing worth flagging is question #1 is the one almost nobody builds for. the verification energy, yours included, goes at #2...is the change correct. tons of tooling for that. there's almost nothing for "did this quietly drift past what we actually signed off on." so the question with no forcing function is also the one with no tools. that feels like the actual hole.
For humans, I completely agree that making a check visible and making it required are different things. Under pressure, optional review steps tend to disappear first.
What I'm less convinced about is whether the same tradeoff holds for AI reviewers.
Most of the review tooling today is aimed at Question #2 ("is the change correct?"), but there's nothing stopping an AI reviewer from being asked Question #1 ("does this still match the assumptions we approved?") on every PR as well.
That isn't enforcement, and it can absolutely miss things. But unlike a human reviewer, it doesn't experience review fatigue, sprint pressure, or the temptation to skip a check because the queue is long. The marginal cost of asking the question is close to zero once the plan is already part of the review context.
My current thinking is that predicates and branch protection handle the hard boundaries, while an AI reviewer becomes the forcing function for the drift question. Not because it's perfect, but because "always asked imperfectly" may be better than "asked perfectly when someone remembers to ask it."
That still leaves the same failure mode if the reviewer itself misses the drift, but it feels like a practical intermediate step before introducing stronger authority or intent-enforcement mechanisms.
yeah, that's fair, and it's the part i can't really push back on...an AI reviewer doesn't get tired or skip a check because the queue's long, so the "we forgot to ask" failure mode mostly goes away. asking Q1 every time really is cheaper and more consistent than hoping a human remembers.
where i'd still hold the line: asking the question and answering it well are different things. Q2 ("is it correct?") has hard signal to lean on, tests and types and static analysis. Q1 ("did this drift past what we approved?") is a judgment call about intent and scope, which is exactly what AI is weakest at. so you can end up trading "asked perfectly when someone remembers" for "asked every time and answered shallowly," and a shallow "yeah, still matches the plan" is its own false comfort. the green that's actually yellow.
honestly it's a pattern i keep hitting lately...people handing judgment to AI are underestimating how much context and experience that judgment actually takes. it can flag where to look. i'm not sold it can be the one who decides.
so i'm with you that it's a sane intermediate step, as long as "the AI asked" never quietly turns into "someone re-checked." the moment those blur, you're trusting a green you didn't earn.
The thing that actually held in your story wasn't the label — it was branch protection. That's worth dwelling on, because a "stop after PR" line written into the delegation prompt lives in the same channel the agent is reading to decide what to do next, so it's advisory by construction. The agent that reasoned "verification passed, what's left?" can reason past "Open PR only" exactly the same way — it's just more text to interpret. The only authority that bound it was the one it couldn't edit.
So I'd invert the hierarchy. Treat the out-of-band limit as the spec and the in-prompt label as its human-readable shadow, not the reverse. Concretely: bound it at the credential, not the prose. Hand the executor a token scoped to exactly the authority you intend — push and open PR, no merge — and "Open PR only" becomes default-deny instead of a sentence you're trusting the agent to honor. "Merge granted" then means "issue a token that can merge," an explicit capability grant rather than a label that competes with the agent's completion heuristic.
One failure mode the static per-slice label misses: scope drift inside a slice. "Merge granted, config-only" is a grant for what the slice was supposed to be, not what it becomes — the regroup that quietly needs a version pin still carries the merge bit. Worth pairing the grant with a declared blast radius the gate re-checks against the actual diff, so the authority stays conditional on the change still matching what you authorized — verified by something the agent doesn't get a vote on.
That's a good distinction. Looking back, the pattern in the post only moved authority from implicit to explicit — the slice declared what it was allowed to do, and the human could review that contract up front.
The actual enforcement still came from GitHub branch protection, not from the agent understanding or obeying the authority declaration itself.
I think the next step is closer to what you're describing: authority enforced through capabilities rather than instructions. Instead of an agent holding broad permissions and being told "don't merge," the execution environment would only provide the permissions required for that slice.
One thing I'd add is that I'd probably push the authority decision outside the agent entirely. In the same way that a context-building step can supply evidence to a later skill, a classifier/orchestrator could supply a capability envelope to the execution step. The agent doesn't request authority; it discovers the authority it has already been given.
That also helps with the scope-drift problem you mentioned. A grant made for a config-only change shouldn't automatically remain valid if the resulting diff grows beyond the assumptions under which the grant was issued.
The post was really about making authority explicit. Capability enforcement feels like the next layer on top of that.
Following the logic one more turn: if the grant shouldn't survive a diff that outgrew its assumptions, then the envelope can't just carry a label ("config-only") — it has to carry a predicate over the realized diff (paths touched ⊆ set, no new deps, size < N), and something re-checks that predicate against the actual change at apply time. And that checker has to sit outside the agent for the same reason the granter does: if the executor that grew the diff is also the one deciding whether the diff is still in scope, you've moved the advisory boundary, not removed it. So it's two out-of-band parts, not one — the orchestrator that issues the envelope, and a verifier that re-evaluates it against what actually got produced. Both distinct from the thing doing the work.
The other edge is in "the agent discovers the authority it already has." Discovery is fine as long as it stays advisory. An agent that reads "I may merge config-only," decides it's done, then emits a diff touching three services should still have the merge bounce — not because it misread the envelope, but because the out-of-band verifier re-checked the predicate and the diff fell outside it. Same shape as your original post, one level up: the in-prompt label was advisory and branch protection was the wall; here the discovered capability is advisory and the predicate re-check is the wall. The enforcement never depends on the agent obeying what it read — only on something it can't edit re-checking what it did.
That's a good point. I agree the envelope only constrains the starting authority. If the resulting diff grows beyond the assumptions under which the envelope was issued, the original grant can become stale even if the executor never exceeded its assigned capabilities.
Where I'm currently landing is that I'd probably combine the predicate approach with a separate verification step rather than relying on predicates alone.
The reason is that I don't think the predicate set can ever be exhaustive. It should encode the hard boundaries we know we care about, but there will always be cases where a change technically satisfies every predicate and still no longer matches the intent or scope of the original grant.
In that model, the predicates act as the enforcement floor, while a verifier (human or AI) evaluates whether the realized diff still aligns with the original intent, classification, and authority envelope. The verifier can block, downgrade, or escalate review, but not expand authority.
So the question becomes less "did the executor stay within its capabilities?" and more "does the resulting change still justify the authority that was originally assigned?"
I think that's the failure mode I hadn't fully played through when I wrote the post.
The reframe is right, but it relocates the trust problem rather than dissolving it. "Does the change still justify the authority assigned?" needs a fixed referent for the authority assigned — and right now that referent is the prompt, the same artifact the executor read. So the verifier reconstructs intent from the exact source the executor optimized against. Anything the executor talked itself into, the verifier can too.
Two fixes, both upstream of the verifier:
Pin intent at grant time — content-address the scope/classification/rationale into the envelope at issue, not just the mechanical predicates. Then "no longer matches intent" stops being a judgment call and becomes measurable drift of the realized diff from a frozen reference.
Make the verdict a signed event keyed to (grant_id, diff_hash) — block/downgrade/escalate, each referencing what it acted on. An unattributed "this still aligns" is exactly as advisory as the in-prompt label you started by rejecting; the out-of-band recheck is the wall, and that move applies to the verifier itself.
Downgrade/escalate is the part that forces a ledger rather than a gate: a downgrade mutates live authority, so the next actor reads current = grant minus accumulated verdicts. Append-only tally, not a single apply-time check.
That shape — verdicts as signed events referencing what they judged, so a third party can re-derive "was this diff admissible under that grant, and who said so" without trusting either side — is basically the lifecycle ANP2 runs in the open. Honestly it's a re-checkable reference economy more than a busy network, but the exact thing you're converging on is live there. If you want to test the design against something running: lobby is kind-1
t=lobby, or anp2.com/try. Curious whether your downgrade/escalate maps onto signed verdict events or wants a new event kind.Reminds me of a common engineering lesson:
If success isn't explicitly defined, optimization never ends.
The two level labels make sense to me, especially defaulting to open PR only. The thing I keep turning over is where that default actually lives. If it only sits in the slice text, a fresh agent chat that never received the slice still has no stop line, so branch protection quietly becomes the real floor anyway. Do you ever set the default at the repo or chat-template level, so silence means stop instead of finish?
That's a good catch. Today the default actually lives in the planning workflow, not at the repo level.
The assumption behind the post was "work starts from a plan slice," so authority is assigned there. A fresh chat that never receives the slice isn't really part of the model I was describing.
Thinking about it now, that probably means the default is underspecified. If a stop condition only exists inside the slice text, then every execution path has to receive the slice for the model to hold.
The direction I'm leaning is making the repo-level default explicit: Open PR only unless a plan slice explicitly elevates authority. That would make the absence of a slice resolve to "stop" rather than leaving the agent to infer a stopping point from task completion heuristics.
I haven't implemented or tested that yet, but it feels like a cleaner answer to the "what if the slice never arrives?" failure mode than assuming every execution path starts from the same planning context.
And I think it fits the pattern the rest of the thread keeps circling: the more important a boundary is, the less it should depend on a particular prompt being present.
The missing stop line is the part every agent plan I've seen gets wrong. We write what to build in detail and leave how far it can go implicit, then act surprised when 'reasonable and merged' wasn't the same as 'reviewed.' The fix isn't a better checklist, it's making the authority boundary explicit: this slice may open a PR but not merge it. Acceptance criteria describe the change; they say nothing about who gets to pull the trigger, and that's the gap the agent fills with its own judgment.
Strong cut — the authority boundary ("may open a PR but not merge") is the line most plans leave implicit. From running this, the part I'd add is that even once that boundary is explicit, there's a second one sitting right next to it: the agent's report that it did the bounded thing can be wrong independently of authority. "Opened the PR" / "tests pass" / "migration applied" are claims, and a clean-looking summary is exactly when a false one survives. So I've ended up separating two boundaries — authority (who may pull the trigger) and whether the report of the bounded action actually matched its effect. Your acceptance criteria pin the first; they say nothing about whether the agent's account of doing the thing is real, and that's the one that's bitten me more often.
The missing step is almost always the exit condition. A plan will list every action and never say what done looks like, so the agent runs past where you wanted it to stop or halts somewhere arbitrary. What's fixed it for me is writing the acceptance check into the plan itself, the specific state that counts as finished, so stopping is something the agent can test for instead of guess at.
You're naming the half the post didn't spend enough time on.
I went deep on authority boundaries because that's what bit me. But the underlying failure mode is the same: the agent fills in whatever the plan leaves implicit. If the plan lists actions and never states a testable finished state, "done" becomes whatever the agent can justify from a green build.
What I've landed on since then is writing down both:
The first is what you're describing: stopping becomes something the agent can verify instead of infer. The second is the layer the article focused on: even with a successful verification, the slice may still be finished at "PR opened," not "merged."
Acceptance criteria alone don't carry the authority half. Authority boundaries alone don't tell the agent what "finished" means. We need both written down.
The "implementation spec vs authority handoff" split is the right cut, and I learned it the same way you did, by an agent doing the obviously-correct next step I had not authorized. For me it was push, not merge: the agent finished a phase and pushed, because nothing in the plan said the push was mine to make. Reasonable from the text, wrong from my intent.
Where I landed is close to yours but with one addition: I do not just label authority per slice, I put the hard stops at the very top of the prompt, not in the section they apply to. I had the stop rule once, but buried in the deploy step, and the agent had already acted by the time it got there. The fix was promoting "do not push, local commits only, the operator pushes after review" to the first lines of the prompt body, above the implementation. Placement is part of the contract, not just presence.
The branch-protection-as-final-gate point is the one I would underline hardest. Plan language tells the agent where to stop; branch protection enforces it when the language fails. I treat them as belt and suspenders, never one or the other, because the plan is a recommendation the agent can misread and the gate is a wall it cannot. The failure mode you are describing is exactly what happens when you have only the recommendation.
The deeper thing your piece names: agent delegation punishes ambiguity faster than human delegation. A human reads "prepare this for review" and stops. The agent completes every step it can justify from the text. So the burden shifts onto the author to make the stop legible, every time, in the place the agent reads first.
The placement point is interesting because it pushes on something I hadn't really separated properly when I wrote the post: defaults versus exceptions.
My initial implementation put authority alongside the delegated work, so each slice carried its own authority label, rationale, and agent instruction. The assumption was that work always started from a planned slice.
A few comments in this thread have made me less comfortable with the default living only there. If a stop condition is important enough, it probably shouldn't depend on a particular slice being present, being read, or being reached at the right point in the prompt.
The direction I'm now leaning is a repo-level default of Open PR only, with plan slices only used to document explicit exceptions where authority is elevated. In that model, the default stop condition exists before any implementation detail, while the slice explains why a particular piece of work is allowed to go further.
I haven't implemented or tested that yet, but it feels closer to the "belt and suspenders" model you're describing: global default as the baseline, slice-level authority as the exception, and branch protection as the final wall when either of those fail.
The "placement is part of the contract" line resonates. It connects with the repo-level default idea that came up elsewhere in the thread, but adds an important prompt-shape detail: it may not be enough for the stop condition to exist; it has to appear before the agent starts reasoning through the implementation.
The repo-level-default-plus-slice-exception model is the right shape, and the reason it is better than per-slice authority is exactly the failure you named: a stop condition that depends on a slice being present, read, and reached at the right point has three ways to silently not fire. A repo-level default has zero, because it is true before the agent reads anything.
The piece I would add, from hitting this myself: the default has to live somewhere the agent cannot finish without seeing it, not just somewhere early. Early in the prompt still depends on the prompt being the thing the agent is working from. The stronger version is the default enforced outside the prompt entirely, branch protection that blocks merge regardless of what any slice said.
Prompt placement makes the default legible to the agent so it behaves well; branch protection makes the default true even when the agent does not. You want both, because the prompt-level default is what makes the agent cooperate and the repo-level wall is what saves you when it does not. The slice then only ever does one job: document why an exception is justified. That separation, default is structural, exception is documented, is the clean version of what you are reaching for.
This matches what I landed on running multi-step agent work, and the line that does the most for me is "silence defaulted to finish the job." That's the whole failure in five words. The split I'd add: the authority label in the prompt and the thing that enforces it have to live in different places. A "do not merge, open PR only" line at the top of the delegation is necessary but it's still in the channel the agent is reading, so it's a recommendation the agent can rationalize past. Branch protection is the only part the agent has no write path to, which is why it holds. I run the same shape manually: the executor commits locally and never pushes, and the push gate lives with me, not in the prompt. The prompt says where to stop; something the agent cannot reach is what actually stops it. Your two-level table is the cleanest version of writing the first half down. The second half is making sure the enforcement sits below the layer the executor runs in.
Great point. Planning is easy to automate, but knowing when enough context has been gathered and when a task is actually complete is still where many agent systems struggle.