Files
PaperClipAI/docs/guides/execution-policy.md
SamandSami Rusani 39666aa906 fix(server): clarify execution policy decision comments (#9105)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Execution policies let issues move through enforced review and
approval stages before work is considered done.
> - Active reviewers and approvers must include decision rationale when
approving or requesting changes.
> - The server already requires the decision comment to arrive in the
same `PATCH /api/issues/:id` request as the status change.
> - When callers post a normal comment first and then send a status-only
`PATCH`, the existing 422 only says a comment is required.
> - This pull request keeps the atomic decision behavior but makes the
API error and docs explicit about the same-request requirement.
> - The benefit is that agents and API clients can recover immediately
by sending `{ status, comment }` together instead of dead-ending on an
ambiguous validation error.

## Linked Issues or Issue Description

Fixes #9049.

Duplicate/related search completed before implementation: searched open
PRs for `9049`, `Approving a review or approval stage requires a
comment`, `Requesting changes requires a comment`, and `same PATCH`. I
did not find a direct open PR for the same error-message/docs fix.
Related PRs found but not duplicates: #8302 documents cross-agent review
gates in the skill API reference, and #5487 covers human approval UI.

## What Changed

- Expanded execution-policy 422 messages for approve and request-changes
decisions to say the comment must be included in the same `PATCH`
request and prior comments are not considered.
- Tightened unit coverage so the comment-required branches assert the
new actionable guidance.
- Documented the same-request `{ status, comment }` decision shape in
the execution policy guide, issue API reference, and agent communication
guide.

## Verification

- `CI=true corepack pnpm --filter @paperclipai/server exec vitest run
src/__tests__/issue-execution-policy.test.ts --reporter verbose`
- `CI=true corepack pnpm --filter @paperclipai/plugin-sdk exec node
../../../scripts/ensure-plugin-build-deps.mjs`
- `CI=true corepack pnpm --filter @paperclipai/server exec tsc --noEmit`
- `git diff --check`

## Risks

Low risk. This does not change execution-policy state transitions or
relax the atomic decision guard; it only makes the existing requirement
explicit in errors and docs.

> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.

## Model Used

OpenAI Codex (GPT-5 Codex), operating in Codex desktop with repository
file access, shell validation, and GitHub CLI workflow. Exact context
window size is not surfaced by this environment.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

Co-authored-by: Sami Rusani <sr@samirusani>
2026-07-30 16:26:35 -07:00

12 KiB

Execution Policy: Review & Approval Workflows

Paperclip's execution policy system ensures tasks are completed with the right level of oversight. Instead of relying on agents to remember to hand off work for review, the runtime enforces review and approval stages automatically.

Overview

An execution policy is an optional structured object on any issue that defines what must happen after the executor finishes their work. It supports three layers of enforcement:

Layer Purpose Scope
Comment required Every agent run must post a comment back to the issue Runtime invariant (always on)
Review stage A reviewer checks quality/correctness and can request changes Per-issue, optional
Approval stage A manager/stakeholder gives final sign-off Per-issue, optional

These layers compose. An issue can have review only, approval only, both in sequence, or neither (just the comment-required backstop).

Data Model

Execution Policy (issue field: executionPolicy)

interface IssueExecutionPolicy {
  mode: "normal" | "auto";
  commentRequired: boolean;       // always true, enforced by runtime
  stages: IssueExecutionStage[];  // ordered list of review/approval stages
}

interface IssueExecutionStage {
  id: string;                                 // auto-generated UUID
  type: "review" | "approval";                // stage kind
  approvalsNeeded: 1;                         // multi-approval is not supported yet
  participants: IssueExecutionStageParticipant[];
}

interface IssueExecutionStageParticipant {
  id: string;
  type: "agent" | "user";
  agentId?: string | null;    // set when type is "agent"
  userId?: string | null;     // set when type is "user"
}

Participants can be either agents or board users. Each stage can have multiple participants; the runtime selects the first eligible participant, preferring any explicitly requested assignee while excluding the original executor.

Execution State (issue field: executionState)

Tracks where the issue currently sits in its policy workflow:

interface IssueExecutionState {
  status: "idle" | "pending" | "changes_requested" | "completed";
  currentStageId: string | null;
  currentStageIndex: number | null;
  currentStageType: "review" | "approval" | null;
  currentParticipant: IssueExecutionStagePrincipal | null;
  returnAssignee: IssueExecutionStagePrincipal | null;
  completedStageIds: string[];
  lastDecisionId: string | null;
  lastDecisionOutcome: "approved" | "changes_requested" | null;
}

Execution Decisions (table: issue_execution_decisions)

An audit trail of every review/approval action:

interface IssueExecutionDecision {
  id: string;
  companyId: string;
  issueId: string;
  stageId: string;
  stageType: "review" | "approval";
  actorAgentId: string | null;
  actorUserId: string | null;
  outcome: "approved" | "changes_requested";
  body: string;              // required comment explaining the decision
  createdByRunId: string | null;
  createdAt: Date;
}

Workflow

Happy Path: Review + Approval

┌──────────┐    executor     ┌───────────┐   reviewer    ┌───────────┐   approver    ┌──────┐
│  todo     │───completes───▶│ in_review  │───approves───▶│ in_review │───approves───▶│ done │
│ (Coder)  │    work         │ (QA)      │               │ (CTO)     │               │      │
└──────────┘                 └───────────┘               └───────────┘               └──────┘
  1. Issue created with executionPolicy specifying a review stage (e.g., QA) and an approval stage (e.g., CTO).
  2. Executor works on the issue in in_progress status.
  3. Executor transitions to done — the runtime intercepts this:
    • Status changes to in_review (not done)
    • Issue is reassigned to the first reviewer
    • executionState enters pending on the review stage
  4. Reviewer reviews and transitions to done with a comment:
    • A decision record is created: { outcome: "approved" }
    • Issue stays in_review, reassigned to the approver
    • executionState advances to the approval stage
  5. Approver approves and transitions to done with a comment:
    • A decision record is created: { outcome: "approved" }
    • executionState.status becomes completed
    • Issue reaches actual done status

Changes Requested Flow

┌───────────┐   reviewer requests   ┌─────────────┐   executor    ┌───────────┐
│ in_review  │───changes────────────▶│ in_progress  │───resubmits──▶│ in_review │
│ (QA)      │                       │ (Coder)      │               │ (QA)      │
└───────────┘                       └──────────────┘               └───────────┘
  1. Reviewer requests changes by transitioning to any status other than done (typically in_progress), with a comment explaining what needs to change.
  2. Runtime automatically:
    • Sets status to in_progress
    • Reassigns to the original executor (stored in returnAssignee)
    • Sets executionState.status to changes_requested
  3. Executor makes changes and transitions to done again.
  4. Runtime routes back to the same review stage (not the beginning), with the same reviewer.
  5. This loop continues until the reviewer approves.

Policy Variants

Review only (no approval stage):

{
  "stages": [
    { "type": "review", "participants": [{ "type": "agent", "agentId": "qa-agent-id" }] }
  ]
}

Executor finishes → reviewer approves → done.

Approval only (no review stage):

{
  "stages": [
    { "type": "approval", "participants": [{ "type": "user", "userId": "manager-user-id" }] }
  ]
}

Executor finishes → approver signs off → done.

Multiple reviewers/approvers: Each stage supports multiple participants. The runtime selects one to act, excluding the original executor to prevent self-review.

Comment Required Backstop

Independent of review stages, every issue-bound agent run must leave a comment. This is enforced at the runtime level:

  1. Run completes — runtime checks if the agent posted a comment for this run.
  2. If no comment: issueCommentStatus is set to retry_queued, and the agent is woken once more with reason missing_issue_comment.
  3. If still no comment after retry: issueCommentStatus is set to retry_exhausted. No further retries. The failure is recorded.
  4. If comment posted: issueCommentStatus is set to satisfied and linked to the comment ID.

This prevents silent completions where an agent finishes work but leaves no trace of what happened.

Run-level tracking fields

Field Description
issueCommentStatus satisfied, retry_queued, or retry_exhausted
issueCommentSatisfiedByCommentId Links to the comment that fulfilled the requirement
issueCommentRetryQueuedAt Timestamp when the retry wake was scheduled

Access Control

  • Only the active reviewer/approver (the currentParticipant in execution state) can advance or reject the current stage.
  • Non-participants who attempt to transition the issue receive a 422 Unprocessable Entity error.
  • Both approvals and change requests require a comment — empty or whitespace-only comments are rejected.
  • The decision comment must be included in the same PATCH /api/issues/{issueId} request that changes the status. A prior POST /api/issues/{issueId}/comments entry remains normal discussion and does not satisfy the review/approval decision guard.

API Usage

Recording review or approval decisions

PATCH /api/issues/{issueId}
{ "status": "done", "comment": "Reviewer decision: approve - implementation and tests look good." }

To request changes, send the target non-done status and the explanation together:

PATCH /api/issues/{issueId}
{ "status": "in_progress", "comment": "Reviewer decision: request changes - cover the retry edge case." }

Do not split a decision into POST /comments followed by a status-only PATCH; the runtime only records the decision from the comment field on the same status update.

Setting an execution policy on issue creation

POST /api/companies/{companyId}/issues
{
  "title": "Implement feature X",
  "assigneeAgentId": "coder-agent-id",
  "executionPolicy": {
    "mode": "normal",
    "commentRequired": true,
    "stages": [
      {
        "type": "review",
        "participants": [
          { "type": "agent", "agentId": "qa-agent-id" }
        ]
      },
      {
        "type": "approval",
        "participants": [
          { "type": "user", "userId": "cto-user-id" }
        ]
      }
    ]
  }
}

Stage IDs and participant IDs are auto-generated if omitted. Duplicate participants within a stage are automatically deduplicated. Stages with no valid participants are removed. If no valid stages remain, the policy is set to null.

Updating execution policy on an existing issue

PATCH /api/issues/{issueId}
{
  "executionPolicy": { ... }
}

If the policy is removed (null) while a review is in progress, the execution state is cleared and the issue is returned to the original executor.

Advancing a stage (reviewer/approver approves)

The active reviewer or approver transitions the issue to done with a comment:

PATCH /api/issues/{issueId}
{
  "status": "done",
  "comment": "Reviewed — implementation looks correct, tests pass."
}

The runtime determines whether this completes the workflow or advances to the next stage.

Requesting changes

The active reviewer transitions to any non-done status with a comment:

PATCH /api/issues/{issueId}
{
  "status": "in_progress",
  "comment": "Button alignment is off on mobile. Please fix the flex container."
}

The runtime reassigns to the original executor automatically.

UI

New Issue Dialog

When creating a new issue, Reviewer and Approver buttons appear alongside the assignee selector. Clicking either opens a participant picker with:

  • "No reviewer" / "No approver" (to clear)
  • "Me" (current user)
  • Full list of agents and board users

Selections build the executionPolicy.stages array automatically.

Issue Properties Pane

For existing issues, the properties panel shows editable Reviewer and Approver fields. Multiple participants can be added per stage. Changes persist to the issue's executionPolicy via the API.

Design Principles

  1. Runtime-enforced, not prompt-dependent. Agents don't need to remember to hand off work. The runtime intercepts status transitions and routes accordingly.
  2. Iterative, not terminal. Review is a loop (request changes → revise → re-review), not a one-shot gate. The system returns to the same stage on re-submission.
  3. Flexible roles. Participants can be agents or users. Not every organization has "QA" — the reviewer/approver pattern is generic enough for peer review, manager sign-off, compliance checks, or any multi-party workflow.
  4. Auditable. Every decision is recorded with actor, outcome, comment, and run ID. The full review history is queryable per issue.
  5. Single execution invariant preserved. Review wakes and comment retries respect the existing constraint that only one agent run can be active per issue at a time.