Review & Merge — Conceptual Deep Dive
Purpose & Mental Model
Agentweaver's review and merge subsystem answers one product-defining question: how can agent-produced work move quickly while preserving human oversight at every irreversible step?
The design separates three concerns that are easy to accidentally blur:
- Workflow declarations identify the review gates and their execution edges.
- Review execution pauses a live run and waits for a decision.
- Merge execution applies an already-reviewed tree to the target branch under repository and database guards.
That separation is the reason the system can support standalone runs, coordinator child runs, automated reviewers, human approval, request-changes loops, and collective assembly without every path inventing its own safety model.
A useful rebuilding rule is: review approves intent to proceed; merge proves the repository can actually accept the result. Approval and merge are related, but they are not the same operation.
Core Design Invariants
These invariants define the subsystem:
- No hidden merge path. Generated changes reach the target branch only through an explicit merge executor or coordinator assembly merge.
- Gates are durable pause points. A human review request is represented in run state and stream events, not only in an in-memory callback.
- Review decisions have arbitration guards. Pending review requests are consumed atomically and status transitions use compare-and-swap. Matching terminal replays can return the existing result; competing active decisions can return conflict. Live and deferred delivery have different ordering, described below.
- Request changes loops back to work. A reviewer can send feedback to the producer instead of choosing between blind approval and terminal rejection.
- Automated review is policy, not authority by itself. RAI and rubberduck gates can pass, request revision, or fail/route the graph, but the human-review gate is the explicit human-oversight point for irreversible actions in the default runtime path.
- Merge is repository-serialized. Even after approval, repository-level merge locks and run-status CAS guards prevent two merges from racing the same base checkout. PostgreSQL deployments use session advisory locks so this guard spans API replicas; SQLite/local development uses a process-wide semaphore.
- Coordinator children do not merge. Child runs produce assemble-ready branches. The parent coordinator assembles, reviews, merges, and records the integrated outcome.
- Fail closed on unbound workflow nodes. An unsupported executable node must fail binding rather than silently disappear.
Workflow-declared review gates
Review gates belong to the selected workflow definition. RunWorkflowFactory.ResolveEffectiveWorkflowAsync resolves the workflow and returns it unchanged; it does not load named project review-policy files or inject a policy overlay. Blueprint validation accepts only review_policy: default (apps/Agentweaver.Api/Runs/RunWorkflowFactory.cs:1495–1517; apps/Agentweaver.Api/Blueprints/BlueprintService.cs:111–115).
Relevant gate kinds include:
- RAI — Responsible AI review. It can pass, request revision, or fail safe on content-safety.
- Rubberduck — automated peer/sanity review. It can pass or request changes.
- Build & Test — automated verification of an applicable assembled code artifact.
- Human review — explicit human approval. It can approve, request changes, or decline.
The runtime binds declared nodes and edges to concrete executors. Compatibility adapters and historical comments mentioning policy-prefixed gates do not establish a configurable registry or composer. See Binding declarative nodes to runtime execution.
The lifecycle below describes a review-bearing standalone workflow, not a guarantee that every custom workflow declares the same gates. Coordinator children use a separate trimmed graph; their parent resolves applicable aggregate gates from its selected workflow.
Human and Automated Reviewers
Agentweaver treats automated and human reviewers as different kinds of gates with the same graph vocabulary.
Automated gates are executable reviewers:
- RAI runs through a Responsible AI reviewer agent and emits a verdict.
- Rubberduck runs through an AI critique reviewer and maps PASS to forward progress and REVISE to request changes.
Human gates are request ports:
- The workflow emits a review request containing the run id, tree hash, diff, step count, and RAI context.
- The watch loop persists the run as awaiting review and stores a pending request.
- The client submits approve, request-changes, or decline.
- The pending request is consumed once and the live workflow resumes on the selected edge.
The key distinction is accountability. Automated reviewers can help decide whether work is ready for a person, but human review is the point where a named user approves or rejects the irreversible action. The run records the reviewer on merge-related status transitions when that reviewer is known.
Single-Run Review Lifecycle
A standalone workflow with RAI, human review, and merge has the conceptual shape:
- The agent produces a tree hash and diff in an isolated worktree.
- RAI reviews the output.
- A human-review request is emitted.
- The run waits in
awaiting_review. - The reviewer chooses approve, request changes, or decline.
- Approval enters merge; request-changes returns to the agent; decline terminates.
The important part is the pause. awaiting_review is not a UI-only label. It is the durable point where the workflow can stop streaming, the browser can disconnect, and a later caller can still see that the run needs a decision.
Approve vs Request Changes
Approve and request-changes both start from the same review gate, but they intentionally diverge.
Approval does not edit files. It authorizes the existing reviewed tree to proceed toward merge. Request-changes does edit the future path: it carries reviewer feedback back into the producer's next turn and increments the revision loop.
For POST /api/runs/{id}/review, the ordering is explicit (apps/Agentweaver.Api/Endpoints/RunEndpoints.cs:845–1053):
| Path | Arbitration and effect |
|---|---|
| Local live approval | Consume the pending request, then send the decision to the workflow. Approval does not CAS the run to merging in the HTTP handler; the merge executor later acquires the repository lock before its merge CAS. |
| Local live request-changes / decline | CAS awaiting_review to in_progress / declined before removing the pending request, then resume the workflow. |
| No local workflow, durable pending request exists | Persist the deferred decision first; request-changes/decline then attempt their status CAS. The owner workflow consumes the deferred response. An approval response labelled merging is not proof that the merge CAS or Git merge has occurred. |
| Neither live workflow nor pending request | Use direct approval/decline fallback. Direct request-changes returns 409 and restores awaiting_review; it cannot reconstruct a revision workflow. |
| Replay / competing caller | Missing pending requests or losing status transitions can return 409; a matching already-merged approval or already-declined non-approval returns the existing terminal result. |
There are two request-changes surfaces:
- The review decision path can send
request_changesthrough the live workflow so the graph loops back. - The dedicated request-changes endpoint validates and sanitizes a comment, records a revision audit row, abandons stale checkpoints, clears run-scoped shell approvals, and starts a fresh revision workflow on the same worktree.
Both preserve the same invariant: after a reviewer rejects the current output, the existing tree is not merged; work returns to a producer path with explicit feedback.
Reviewer Rejection and Lockout
The implemented lockout model has two layers.
First, workflow production and review are distinct responsibilities. In a review-bearing workflow, the agent executor proposes a tree; the review decision resumes the graph toward revision, decline, or guarded merge. This is not a claim that every custom workflow enforces two-person approval.
Second, pending consumption and status transitions arbitrate active decisions. A consumed live gate cannot accept another response. Losing CAS operations return conflict, while matching already-terminal replays can return the recorded result. These guards are not a separate agent-author rotation rule.
The approval and request-changes sequence shows the shared pending-request and CAS arbitration; a second lockout diagram would duplicate it.
The standalone endpoint requires run access at ProjectRole.Contributor; an additional pending-request owner check applies to projectless legacy runs (RunEndpoints.cs:875, :936–938, :1008–1011). The coordinator assembly gate has its own owner-scoped delivery contract. Neither is a general two-person rule preventing a human requester from approving their own run. Coordinator agent-author rotation is a different mechanism: a resumable rejected target may keep its author; a fresh-dispatch decision attempts scoped rotation, with a bounded same-author fallback when no alternate is eligible. See resilient reviewer rejection.
Coordinator Collective Review
Coordinator orchestration changes the unit of review. Child runs do not individually ask for human approval and do not merge. They stop at assemble-ready, carrying branch, tree hash, diff, and safety context back to the parent.
The parent coordinator then performs one collective assembly pipeline:
- Ensure all subtasks are assembly-eligible.
- Build an integration branch from child branches in dependency order.
- Resolve workflow-declared aggregate gates and Build & Test applicability; run those gates in their resolved order.
- At the authored human gate, persist one review request over the combined output. Collective RAI RED also parks at durable human review; RAI REVISE enters explicit steering, with durable human escalation when the decider chooses
Proceed. - On approval, continue any remaining authored gates, then merge the integration branch into the originating branch and run Scribe.
- On request-changes, scope structured target files and dependent rebuilds, then let the coordinator explicitly choose in-place revision, fresh dispatch, escalation, or advisory continuation. Do not infer a reset from feedback prose.
- On decline, terminalize the coordinator run as declined.
This design avoids a misleading review experience. Reviewing child diffs independently can miss cross-child interactions. The meaningful artifact is the integrated whole, so the human sees and approves the combined output.
How Merge Actually Happens
Merge is deliberately more mechanical than review.
For a standalone run, the merge coordinator:
- Canonicalizes and validates the repository path.
- Acquires a repository merge lock.
- Attempts the transition from
awaiting_revieworcommittingtomerging; if the CAS loses, it permits an already-mergingrun while holding the repository lock, otherwise releases the lock and fails (MergeCoordinator.cs:50–62). - Merges the worktree branch into the originating branch, verifying the expected tree hash.
- On success, records
merged, stores the merge commit hash, and removes the worktree. - On conflict, records
merge_failed, stores conflicting files, and preserves the worktree for inspection. - On a retryable blocked outcome or internal fail-safe, reverts back to
awaiting_reviewwhen possible.
For coordinator assembly, the integration branch is the source, merged into the originating branch. A successful assembly merge terminalizes the coordinator run as completed with an assembly-complete reason rather than as a normal standalone merged run. That difference matters: the parent run represents an orchestration outcome, not a single worker's branch.
Failure Modes and How to Reason About Them
Workflow cannot resolve or bind
Invalid workflow resolution throws WorkflowBindException; executable nodes must bind to supported runtime behavior. There is no project policy-file discovery or overlay fallback to diagnose.
Reasoning model: a declared gate must execute, not merely appear in configuration.
Review decision races
Two clients may submit decisions at nearly the same time. The pending request store and run-status CAS decide one winner. The loser sees conflict/no-pending behavior.
Reasoning model: review is an ownership transfer from waiting workflow to exactly one decision.
Workflow disappears after restart
If a run is awaiting review but no live workflow can be resumed, the direct fallback can approve or decline using merge infrastructure. Request-changes is not supported on that direct path because there is no live workflow to resume; the run is restored to awaiting review so the caller can choose approve or decline.
Reasoning model: fallback can finish an irreversible path, but it should not pretend it can reconstruct a revision loop without a workflow.
Merge is blocked
A blocked merge can return to awaiting review instead of failing terminally. This lets a human retry once the repository constraint clears.
Reasoning model: "approved" means the user accepted the diff, not that the repository was guaranteed writable at that instant.
Merge conflicts
Conflicts become merge_failed, with conflict details and the worktree preserved where applicable.
Reasoning model: conflicts are not safe to auto-resolve under the review approval. The reviewed tree and the target branch no longer compose cleanly.
Coordinator assembly has ineligible children
The assembly pipeline blocks before building or merging a partial result.
Reasoning model: collective review is all-or-nothing. A missing or failed child means the integrated output is not the reviewed outcome.
Coordinator review survives a long wait or restart
The human assembly gate waits indefinitely until a decision or cancellation. Shutdown leaves in_review recoverable. ResumeInReviewAsync uses the persisted integration branch and tree hash: it applies a persisted decision or re-arms the gate without rebuilding. Only missing/incomplete review metadata falls back to rebuilding (CoordinatorAssemblyService.cs:1321–1385).
Reasoning model: an explicit, durable human wait is not a failed autonomous worker.
Trade-offs
- Declared review vs policy injection. Gate changes belong to workflow definitions; there is no separate configurable safety overlay.
- Run access vs separate reviewer assignment. Project contributor access and legacy/assembly owner checks are not two-person approval. That would require a separate persisted assignment model and enforcement.
- Single collective review vs per-child review. Collective review gives a truthful integrated diff. It delays human feedback until fan-in; structured target files and explicit steering limit the scope of rework.
- Direct fallback vs no fallback. Fallback lets approval/decline complete after some restart scenarios. It intentionally does less than the live workflow to avoid inventing state.
- Blocked merge returns to review. This keeps runs recoverable, but clients must understand that approval can lead back to an awaiting-review state rather than a terminal result.
Rebuilding Blueprint
If you were rebuilding this subsystem from scratch, implement these pieces in order:
- Define durable run statuses: in progress, awaiting review, merging, merged, merge failed, declined, completed, failed, and coordinator assembly states.
- Implement a workflow request gate that can pause execution and emit a durable review request.
- Store pending review requests with owner identity and at-most-once consumption.
- Implement review decisions: approve, request changes, decline.
- Make request-changes feed sanitized reviewer feedback back into the producer and clear stale approvals/checkpoints.
- Declare review gates and their decision edges in validated workflow definitions.
- Bind declared gates to executable behavior; fail closed on unsupported bindings.
- Implement merge with both a repository lock and run-status CAS; use a distributed lock when multiple API replicas can serve the same project workspace.
- Preserve conflict details and recoverable blocked states distinctly.
- Trim coordinator child runs so they produce assemble-ready output only.
- Resolve applicable authored aggregate gates, persist human review, then merge the integration source into the originating branch and run Scribe.
- Route structured request-changes through an explicit steering decision; preserve in-place context where resumable and scope fresh work to implicated subtasks plus dependents.
- Persist review/merge events so reload, reconnect, and postmortem inspection see the same story.
The central design principle is simple: agents can propose and revise, automated reviewers can critique, but irreversible repository change passes through explicit review and guarded merge.
Where this lives
apps/Agentweaver.Api/Endpoints/RunEndpoints.csapps/Agentweaver.Api/Endpoints/CoordinatorEndpoints.csapps/Agentweaver.Api/Runs/apps/Agentweaver.Api/Workflows/apps/Agentweaver.Api/Coordinator/packages/Agentweaver.AgentRuntime/Workflow/packages/Agentweaver.Domain/
Diagram details and constraints
| Element | Contract |
|---|---|
| title | Collective assembly and review |
| takeaway | RED parks durably for a human. REVISE enters explicit steering, not RaiBlocked. |
| group-title-0 | CLAIM AND AGGREGATE |
| group-title-1 | AUTHORED CHECKS AND HUMAN WAIT |
| group-title-2 | STEERING, RECOVERY AND COMPLETION |
| Claim + eligibility | Claim + eligibility |
| Claim + eligibility | No partial failed plan |
| Claim + eligibility | awaiting -> assembling |
| Integration snapshot | Integration snapshot |
| Integration snapshot | Ordered child branches |
| Integration snapshot | branch / tree / diff |
| Applicable gates | Applicable gates |
| Applicable gates | Workflow-defined ordering |
| Applicable gates | non-code: omit build |
| Gate outcomes | Gate outcomes |
| Gate outcomes | Pass: next; REVISE: steer |
| Gate outcomes | RAI RED: human park |
| Normal human gate | Normal human gate |
| Normal human gate | Persist request, then wait |
| Normal human gate | approve: next gates |
| Safety / budget park | Safety / budget park |
| Safety / budget park | Durable human escalation |
| Safety / budget park | in_review / awaiting |
| Explicit steering | Explicit steering |
| Explicit steering | In-place, fresh or advisory |
| Explicit steering | Proceed: human park |
| Recovered review | Recovered review |
| Recovered review | Use saved branch and tree |
| Recovered review | no routine rebuild |
| Approved completion | Approved completion |
| Approved completion | Lock, merge, then Scribe |
| Approved completion | Scribe error: nonfatal |
| e0 | eligible |
| e1 | snapshot |
| e2 | check |
| e3 | pass |
| e4 | human |
| e5 | approve |
| e6 | RED |
| e7 | REVISE |
| e8 | changes |
| e9 | Proceed |
| e10 | revision |
| e11 | recover |
| e12 | approved |
| e13 | all done |
| groups | CLAIM AND AGGREGATE; AUTHORED CHECKS AND HUMAN WAIT; STEERING, RECOVERY AND COMPLETION |
Diagram details and constraints
| Element | Contract |
|---|---|
| title | Review authorizes; merge still guards |
| takeaway | A review-bearing standalone workflow declares its gates; approval alone does not edit Git. |
| group-title-0 | WORKFLOW AND CANDIDATE |
| group-title-1 | REVIEW ALTERNATIVES |
| group-title-2 | CONTINUATION AND GIT RESULT |
| Selected definition | Selected definition |
| Selected definition | Bind the authored graph |
| Selected definition | no injected project policy |
| Producer output | Producer output |
| Producer output | Capture tree and diff |
| Producer output | reviewable candidate |
| Declared review gate | Declared review gate |
| Declared review gate | Only when workflow includes it |
| Declared review gate | not universal to all graphs |
| Request changes | Request changes |
| Request changes | Return feedback to execution |
| Request changes | revision path |
| Approve | Approve |
| Approve | Allow workflow continuation |
| Approve | not direct file mutation |
| Decline | Decline |
| Decline | Persist declined terminal |
| Decline | no merge authorization |
| Continuation | Continuation |
| Continuation | Deliver workflow response |
| Continuation | remaining authored nodes |
| Merge coordinator | Merge coordinator |
| Merge coordinator | Lock and reviewed-tree guard |
| Merge coordinator | CAS before Git operation |
| Actual merge result | Actual merge result |
| Actual merge result | Merged, blocked or conflict |
| Actual merge result | internal errors distinct |
| e0 | execute |
| e1 | review |
| e2 | changes |
| e3 | approve |
| e4 | decline |
| e5 | feedback |
| e6 | continue |
| e7 | on merge |
| e8 | result |
| groups | WORKFLOW AND CANDIDATE; REVIEW ALTERNATIVES; CONTINUATION AND GIT RESULT |
Diagram details and constraints
| Element | Contract |
|---|---|
| title | Guarded standalone merge |
| takeaway | Repository locking precedes CAS; reviewed-tree mismatch and conflicts are not success. |
| group-title-0 | INPUT AND LOCK ADMISSION |
| group-title-1 | STATUS GUARD AND GIT |
| group-title-2 | OUTCOMES AND RELEASE |
| Reviewed input | Reviewed input |
| Reviewed input | Canonicalize repository path |
| Reviewed input | reviewed source + tree |
| Repository lock | Repository lock |
| Repository lock | Bounded acquisition wait |
| Repository lock | 5-second wait |
| Repository busy | Repository busy |
| Repository busy | No acquired lock |
| Repository busy | LockFailed |
| TryStartMerging CAS | TryStartMerging CAS |
| TryStartMerging CAS | Reload if CAS loses |
| TryStartMerging CAS | already Merging may proceed |
| Guarded Git operation | Guarded Git operation |
| Guarded Git operation | Reviewed tree into origin |
| Guarded Git operation | while holding lock |
| Merged | Merged |
| Merged | Persist commit and status |
| Merged | best-effort cleanup |
| Blocked / conflict | Blocked / conflict |
| Blocked / conflict | Blocked: restore review |
| Blocked / conflict | conflict: MergeFailed |
| Internal error | Internal error |
| Internal error | Filtered exception handling |
| Internal error | revert / internal error |
| Release acquired lock | Release acquired lock |
| Release acquired lock | Every acquired-lock exit |
| Release acquired lock | finally |
| e0 | validate |
| e1 | busy |
| e2 | locked |
| e3 | allowed |
| e4 | merged |
| e5 | blocked |
| e6 | error |
| e10 | denied |
| groups | INPUT AND LOCK ADMISSION; STATUS GUARD AND GIT; OUTCOMES AND RELEASE |
Diagram details and constraints
| Element | Contract |
|---|---|
| title | Review API decision paths |
| takeaway | Authorize first. Deliver through the right path. Lock before any merge CAS. |
| group-title-0 | ADMISSION AND REPLAY |
| group-title-1 | DELIVERY ALTERNATIVES |
| group-title-2 | CONTINUATION AND MERGE |
| Caller + access | Caller + access |
| Caller + access | Project contributor check |
| Caller + access | legacy: pending owner |
| Reviewable state? | Reviewable state? |
| Reviewable state? | Inspect status + pending |
| Reviewable state? | awaiting_review |
| Replay or conflict | Replay or conflict |
| Replay or conflict | Matching terminal: reuse |
| Replay or conflict | otherwise: 409 |
| Live pending | Live pending |
| Live pending | Changes / decline use CAS |
| Live pending | approve: no merge CAS |
| Deferred pending | Deferred pending |
| Deferred pending | Persist the decision first |
| Deferred pending | then status transition |
| No live / no pending | No live / no pending |
| No live / no pending | Validate direct approval |
| No live / no pending | changes: 409 |
| Consume + deliver | Consume + deliver |
| Consume + deliver | Send workflow response |
| Consume + deliver | live continuation |
| Repository lock | Repository lock |
| Repository lock | Only on reaching merge |
| Repository lock | lock before CAS |
| Merge CAS + Git | Merge CAS + Git |
| Merge CAS + Git | Guard reviewed tree input |
| Merge CAS + Git | release lock on exit |
| e0 | check |
| e1 | replay |
| e2 | live |
| e3 | deferred |
| e4 | direct |
| e5 | deliver |
| e6 | on merge |
| e7 | approve |
| e8 | locked |
| groups | ADMISSION AND REPLAY; DELIVERY ALTERNATIVES; CONTINUATION AND MERGE |
