mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-28 12:04:41 +01:00
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - Native runs report their outcome through paperclip_finish. > - The server previously turned incomplete reports into human approval requests. > - Those requests could block a later successful run, even when no person had requested review. > - This pull request creates review cards only for explicit attention requests and withdraws proven old fallback cards. > - Agents receive useful completion feedback, while explicit approval gates and task state protections remain in force. ## Linked Issues or Issue Description Related: #13266 removed reviews caused by policy upgrades. This change removes the separate completion fallback. **What happened?** An agent reported needs_review while waiting for checks without requesting a human decision. Paperclip created a generic Native completion review. A later successful report could not complete the task because that old card remained pending. **Expected behavior** Ordinary low-risk work completes after a valid done report, a successful run, and workspace finalization. Incomplete work stays with the agent. Explicit approval requests remain visible and must be resolved. **Steps to reproduce** 1. Complete a native run with needs_review and no attention requests. 2. Continue the task and submit a successful done report. 3. Observe that the old implementation leaves the task in review behind a generic confirmation card. ## What Changed - Require explicit attention requests to create native review cards. Route each request independently and preserve pending or declined decisions. - Withdraw only pending system cards with matching old decision, assessment, effect, contract, and prompt provenance. Preserve history and explicit or answered requests. - Reassess an affected current result without overwriting later task edits, runs, contracts, or workspace failures. - Return pending approval links and required actions through the completion tool. Reject contradictory done reports and empty review requests before accepting a result. - Allow one corrective continuation for incomplete results, then expose a recovery action. - Update status fixtures, database regressions, runner tests, and the completion contract documentation. ## Verification - `pnpm -r typecheck` passed after merging current master. - `pnpm build` passed after merging current master. - The combined branch passed 89 completion and Agent Chat tests. Other targeted tests passed: 170 external-chat and reconciliation tests; 50 runner-resume and control-plane tests; 13 arbiter tests; 7 chat delivery tests; 21 runner completion and runtime-context tests. - The full local test attempt exposed old review fixtures and a missing fake-provider binary. The fixtures are fixed and the helper is built. All affected suites pass in fresh reruns. The timing-sensitive Discord test also passed on rerun. - All latest-head CI checks passed, including build, typecheck, general and serialized tests, runner verification, browser tests, and canary dry run. Greptile is 5/5 with zero unresolved comments. ## Risks - Cleanup changes existing pending cards. It requires exact system provenance and only applies to low-risk agent-claim contracts. It does not delete history or dismiss explicit requests. - Status still commits after the turn and workspace finalization. Completion feedback reports current constraints and does not claim an early status commit. - Incomplete reports now request a bounded corrective run instead of an automatic approval. Repeated failures expose recovery. ## Model Used OpenAI Codex, GPT-6, with repository inspection, code execution, and test tools. The exact deployment identifier and context window size are not exposed in this session. ## 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 - [x] All Paperclip CI gates are green - [x] 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: Paperclip <noreply@paperclip.ing>
314 lines
13 KiB
Markdown
314 lines
13 KiB
Markdown
# Native status arbitration
|
|
|
|
Native runner results do not directly mutate an issue's status. The model may
|
|
report that work is done, blocked, ready for review, or yielded, but Paperclip's
|
|
server remains the authority that decides and commits the resulting workflow
|
|
state.
|
|
|
|
The native status pipeline is:
|
|
|
|
```text
|
|
structured runner result
|
|
|
|
|
v
|
|
schema and terminal validation
|
|
|
|
|
v
|
|
evidence classification
|
|
|
|
|
v
|
|
pure status arbitration
|
|
|
|
|
v
|
|
transactional decision commit
|
|
|
|
|
+--> issue status/version
|
|
+--> durable side effects
|
|
+--> audit and recovery records
|
|
```
|
|
|
|
This separation prevents model prose from acting as a privileged status
|
|
command, protects newer issue state from stale runs, and makes every decision
|
|
replayable and auditable.
|
|
|
|
## Inputs to finalization
|
|
|
|
The runner returns a `paperclip.run_result.v1` result and a matching terminal
|
|
record. Important result fields include:
|
|
|
|
- `reportedWorkDisposition`: `done`, `blocked`, `needs_review`, or `yielded`;
|
|
- `completionClaim`, including the contract revision, criterion claims, and
|
|
remaining work;
|
|
- `verification` claims;
|
|
- evidence references;
|
|
- an optional blocker; and
|
|
- an optional continuation.
|
|
|
|
The server also owns facts the runner cannot choose:
|
|
|
|
- the persisted completion contract;
|
|
- the run's actual terminal state;
|
|
- whether workspace finalization succeeded;
|
|
- the issue's current status and status version;
|
|
- pending approvals, interactions, and execution-policy stages; and
|
|
- the completion-authority policy recorded on the contract.
|
|
|
|
## Evidence classification
|
|
|
|
`classifyNativeEvidence()` compares model claims with durable Paperclip
|
|
records. It recognizes these evidence families:
|
|
|
|
- run events with an authoritative control-plane evidence verdict;
|
|
- issue work products;
|
|
- approvals;
|
|
- issue-thread interactions; and
|
|
- attachments.
|
|
|
|
Each evidence reference becomes one of:
|
|
|
|
| Outcome | Meaning |
|
|
| --- | --- |
|
|
| `accepted` | A matching durable record exists and authoritatively supports the claim. |
|
|
| `missing` | The required claim or referenced record does not exist or is still pending. |
|
|
| `rejected` | The record or claim explicitly contradicts completion. |
|
|
| `unverifiable` | A record exists, or a string was supplied, but it is not authoritative evidence. |
|
|
|
|
For example, a model-authored reference such as `task-response` is not trusted
|
|
merely because it looks descriptive. Likewise, the model's own
|
|
`run.result.proposed` event is a claim, not independent proof.
|
|
|
|
The classifier produces a `NativeEvidenceAssessment` containing:
|
|
|
|
- contract-revision validity;
|
|
- criterion claim and evidence outcomes;
|
|
- verification claim and evidence outcomes;
|
|
- accepted, missing, rejected, and unverifiable references;
|
|
- blocking remaining work;
|
|
- normalized blocker or continuation data; and
|
|
- pending attention requests.
|
|
|
|
The classifier does not update the issue.
|
|
|
|
## Completion authority
|
|
|
|
The persisted completion contract controls how a `done` claim may be accepted.
|
|
|
|
### Durable-evidence completion
|
|
|
|
The strongest completion path requires all of the following:
|
|
|
|
- the runner reports `done`;
|
|
- the objective is satisfied;
|
|
- every contract criterion has accepted durable evidence;
|
|
- every verification has accepted durable evidence; and
|
|
- no remaining work blocks completion.
|
|
|
|
This produces `completion_contract_satisfied`.
|
|
|
|
### Low-risk claim-policy completion
|
|
|
|
Ordinary issue completion changes Paperclip workflow state, but it does not by
|
|
itself authorize deployments, spending, secret access, approvals, or arbitrary
|
|
API calls. Default native completion contracts therefore use low-risk
|
|
`agent_claim_policy` authority.
|
|
|
|
Under that policy, a result can complete the issue when:
|
|
|
|
- it reports `done`;
|
|
- its contract revision matches;
|
|
- every contract criterion is claimed `satisfied`;
|
|
- every verification is claimed `passed`; and
|
|
- no remaining work blocks completion.
|
|
|
|
This produces `completion_claim_policy_accepted`. Independently governed tools
|
|
and effects still enforce their own authorization and approval rules.
|
|
|
|
## Decision order
|
|
|
|
`arbitrateNativeStatus()` is a pure function. It evaluates higher-authority
|
|
conditions before model disposition:
|
|
|
|
| Condition | Status decision | Important effects/reason |
|
|
| --- | --- | --- |
|
|
| Issue is already `done` or `cancelled` | Preserve | `terminal_status_preserved` |
|
|
| Workspace finalization failed | Preserve | Record a retryable finalization error |
|
|
| Run was cancelled | Preserve | Release run resources |
|
|
| Run failed | Preserve | Schedule recovery |
|
|
| Approval, interaction, or execution stage is pending | `in_review` | Materialize/bind the governance gate and notify its owner |
|
|
| Completion satisfies its authority policy | `done` | Release checkout |
|
|
| Runner reports a concrete attention request with a reviewer and decision | `in_review` | Bind the requested reviewer |
|
|
| Runner reports `needs_review` without a decision, or an incomplete completion claim | Keep work with the agent | No automatic human approval; at most one corrective continuation, then a visible recovery action |
|
|
| Runner reports a task-wide blocker | `blocked` | Persist blocker owner and unblock action |
|
|
| Runner reports a current-track blocker | `in_progress` | Enqueue another productive track |
|
|
| Runner reports `yielded` with a valid continuation | `in_progress` | Enqueue the declared continuation |
|
|
| Completion evidence is incomplete and continuation is forbidden | Preserve | Record a finalization error and named next action |
|
|
| Completion evidence is otherwise incomplete | `in_progress` | Enqueue a bounded, idempotent continuation |
|
|
|
|
The output is a `NativeStatusDecision` containing:
|
|
|
|
- the arbiter policy version;
|
|
- `statusAction` and `toStatus`;
|
|
- a stable reason code;
|
|
- an optional unblock descriptor; and
|
|
- declarative side effects.
|
|
|
|
The pure arbiter performs no database or network writes.
|
|
|
|
## Transactional decision commit
|
|
|
|
`commitNativeStatusDecision()` applies the decision against authoritative issue
|
|
state. It uses the issue's prior status, status version, and prior decision ID
|
|
as compare-and-swap inputs. If another actor changed the issue first, the commit
|
|
raises `NativeStatusRaceError`; finalization reloads current state, reassesses,
|
|
and retries a bounded number of times.
|
|
|
|
Within the transaction, the committer:
|
|
|
|
1. validates that the assessment and issue bindings still match;
|
|
2. records the status decision and reason;
|
|
3. updates the issue status and increments its version when required;
|
|
4. materializes declared effects;
|
|
5. writes an effect ledger with deterministic idempotency keys;
|
|
6. updates the native-finalization coordinator; and
|
|
7. persists audit activity.
|
|
|
|
After commit, activity publications are emitted. Reconciliation may redeliver a
|
|
pending effect, but it resumes the recorded ledger decision; it does not ask the
|
|
model or arbiter to invent a new decision.
|
|
|
|
## Durable side effects
|
|
|
|
Depending on the decision, materialized effects may include:
|
|
|
|
- an idempotent agent wake request;
|
|
- an issue-thread interaction;
|
|
- a reviewer binding or owner notification;
|
|
- a persisted blocker and unblock action;
|
|
- a scheduled retry or recovery action;
|
|
- a delegated child issue;
|
|
- checkout/resource release; or
|
|
- finalization/reconciliation records.
|
|
|
|
Effect rows bind company, issue, decision, target, ordinal, delivery state, and
|
|
idempotency key. This is what makes a status transition with follow-up work
|
|
recoverable after a process crash.
|
|
|
|
## Governance precedence
|
|
|
|
A model cannot bypass a pending governance gate by reporting `done`. Before
|
|
completion is considered, finalization checks:
|
|
|
|
- an active execution-policy stage;
|
|
- a pending issue-thread interaction; and
|
|
- a pending or revision-requested approval linked to the issue.
|
|
|
|
If one exists, the issue goes to `in_review` and the durable gate remains the
|
|
path forward.
|
|
|
|
## Failure and recovery behavior
|
|
|
|
Status finalization is coordinated by `native_run_finalizations`. Important
|
|
phases include observation, workspace finalization, assessment, arbitration,
|
|
commit, and retryable or terminal failure.
|
|
|
|
Examples:
|
|
|
|
- a workspace-finalization failure preserves the claim and records a retryable
|
|
error rather than falsely completing the issue;
|
|
- a failed provider run preserves partial evidence and schedules recovery;
|
|
- a status-version race causes bounded reassessment against current issue
|
|
state; and
|
|
- a materialization failure records the failed phase and next retry time rather
|
|
than silently dropping the side effect.
|
|
|
|
## Policy upgrades
|
|
|
|
The policy version on an assessment is audit metadata. New runs use the current
|
|
rules. A version change alone does not reassess an old run, change task status,
|
|
or ask a person to review completion. New evidence and explicit status changes
|
|
still use the existing reconciliation paths.
|
|
|
|
Reconciliation also withdraws pending review cards created solely by the old
|
|
policy-version check. It restores the previous status only if that exact decision
|
|
and status version are still current and no other review gate is pending. A later
|
|
user or agent decision takes precedence. The old assessments and decisions remain
|
|
in the audit history; cleanup does not accept or reject the agent's work.
|
|
|
|
## Diagnosing an unexpected status
|
|
|
|
Start with the terminal heartbeat run and inspect:
|
|
|
|
1. `resultJson.nativeResult.reportedWorkDisposition`;
|
|
2. completion-claim contract revision and criterion statuses;
|
|
3. verification statuses and evidence references;
|
|
4. `resultJson.assessmentId` and `decisionId`;
|
|
5. `resultJson.authoritativeDecision`;
|
|
6. `resultJson.issueStatusBefore` and `issueStatusAfter`;
|
|
7. `finalizationPhase` and `workspaceFinalizeStatus`; and
|
|
8. pending approvals, interactions, execution stages, blockers, or
|
|
continuations on the issue.
|
|
|
|
Common patterns:
|
|
|
|
- `done` claim + `in_progress` decision: evidence/claim policy did not accept
|
|
completion, or blocking work remained;
|
|
- `done` claim + `in_review`: a governance gate took precedence;
|
|
- successful run + unchanged status + retryable finalization: workspace or
|
|
status-effect commit failed;
|
|
- repeated `issue_continuation_needed` runs: the issue remains open without a
|
|
converging completion or durable wait path; and
|
|
- terminal issue unchanged by a stale run: terminal-state preservation or
|
|
status-version race protection worked as designed.
|
|
|
|
## Primary implementation locations
|
|
|
|
- `server/src/services/native-runtime/evidence-classifier.ts` — validates
|
|
claims against durable records.
|
|
- `server/src/services/native-runtime/status-arbiter.ts` — pure policy and
|
|
decision table.
|
|
- `server/src/services/native-runtime/status-decision-committer.ts` — CAS
|
|
commit, effect materialization, ledger, and audit.
|
|
- `server/src/services/native-runtime/native-run-finalizer.ts` — orchestrates
|
|
assessment, governance checks, arbitration, bounded race retry, and result
|
|
projection.
|
|
- `server/src/services/native-runtime/completion-contracts.ts` — creates and
|
|
versions default native completion contracts.
|
|
- `server/src/services/native-runtime/native-finalization-reconciler.ts` —
|
|
resumes interrupted finalization without re-inventing committed decisions.
|
|
- `server/src/services/recovery/service.ts` — repairs open issues that finish
|
|
without a live or durable wait path.
|
|
|
|
See also
|
|
[`durable-continuation-scheduler.md`](./durable-continuation-scheduler.md) for
|
|
the scheduler and recovery behavior that follows an `in_progress` decision.
|
|
|
|
## Explicit completion reviews
|
|
|
|
Ordinary task completion uses the agent's structured `done` claim under the
|
|
contract's low-risk claim policy. Unknown evidence references remain diagnostic
|
|
information; they do not create human approval requirements. Cancellation,
|
|
newer task state, unresolved dependencies, and explicit governance still win.
|
|
|
|
Paperclip no longer creates a generic "Native completion review" because a
|
|
report is incomplete, verification failed, or the agent says `needs_review`.
|
|
A new review interaction requires an explicit attention request naming the
|
|
reviewer's responsibility and the decision. The card displays that request.
|
|
Waiting for CI remains agent work, not a human completion approval.
|
|
|
|
The native runner returns current approval/dependency constraints to the agent
|
|
when it calls `paperclip_finish`. An empty `needs_review` report without an
|
|
existing gate is rejected with instructions to correct it. The final reply must
|
|
explain any required user action and link to the relevant task or approval.
|
|
The tool acknowledges receipt, not a premature status commit: final status is
|
|
committed only after the provider turn and workspace finalization settle.
|
|
|
|
On upgrade, bounded cleanup withdraws only unanswered, system-created fallback
|
|
cards proven by their decision/effect ledger, original prompt/target, empty
|
|
attention request list, and low-risk claim policy. Explicit or answered reviews
|
|
and stronger completion policies are preserved. Withdrawal has audit history
|
|
and retires chat actions. Reconciliation reassesses only the current successful
|
|
run's result, with the same task status/version and completion contract and no
|
|
newer execution owner. It applies normal governance and dependency checks and
|
|
appends a decision; it never marks every affected task done blindly. A persisted
|
|
withdrawal marker makes restart between cleanup and reassessment retryable.
|