Merge pull request #2772 from paperclipai/PAPA-46-why-did-this-issue-succeed-without-following-my-instructions

fix: enable agent re-checkout of in_review tasks on comment feedback
This commit is contained in:
Dotta
2026-04-06 18:57:33 -05:00
committed by GitHub
10 changed files with 26 additions and 23 deletions
+1 -1
View File
@@ -491,7 +491,7 @@ All endpoints are under `/api` and return JSON.
```json
{
"agentId": "uuid",
"expectedStatuses": ["todo", "backlog", "blocked"]
"expectedStatuses": ["todo", "backlog", "blocked", "in_review"]
}
```
+1 -1
View File
@@ -73,7 +73,7 @@ POST /api/issues/{issueId}/checkout
Headers: X-Paperclip-Run-Id: {runId}
{
"agentId": "{yourAgentId}",
"expectedStatuses": ["todo", "backlog", "blocked"]
"expectedStatuses": ["todo", "backlog", "blocked", "in_review"]
}
```
@@ -31,14 +31,14 @@ Close linked issues if the approval resolves them, or comment on why they remain
### Step 3: Get Assignments
```
GET /api/companies/{companyId}/issues?assigneeAgentId={yourId}&status=todo,in_progress,blocked
GET /api/companies/{companyId}/issues?assigneeAgentId={yourId}&status=todo,in_progress,in_review,blocked
```
Results are sorted by priority. This is your inbox.
### Step 4: Pick Work
- Work on `in_progress` tasks first, then `todo`
- Work on `in_progress` tasks first, then `in_review` when you were woken by a comment on it, then `todo`
- Skip `blocked` unless you can unblock it
- If `PAPERCLIP_TASK_ID` is set and assigned to you, prioritize it
- If woken by a comment mention, read that comment thread first
@@ -50,7 +50,7 @@ Before doing any work, you must checkout the task:
```
POST /api/issues/{issueId}/checkout
Headers: X-Paperclip-Run-Id: {runId}
{ "agentId": "{yourId}", "expectedStatuses": ["todo", "backlog", "blocked"] }
{ "agentId": "{yourId}", "expectedStatuses": ["todo", "backlog", "blocked", "in_review"] }
```
If already checked out by you, this succeeds. If another agent owns it: `409 Conflict` — stop and pick a different task. **Never retry a 409.**
+4 -4
View File
@@ -11,7 +11,7 @@ Before doing any work on a task, checkout is required:
```
POST /api/issues/{issueId}/checkout
{ "agentId": "{yourId}", "expectedStatuses": ["todo", "backlog", "blocked"] }
{ "agentId": "{yourId}", "expectedStatuses": ["todo", "backlog", "blocked", "in_review"] }
```
This is an atomic operation. If two agents race to checkout the same task, exactly one succeeds and the other gets `409 Conflict`.
@@ -82,8 +82,8 @@ This releases your ownership. Leave a comment explaining why.
```
GET /api/agents/me
GET /api/companies/company-1/issues?assigneeAgentId=agent-42&status=todo,in_progress,blocked
# -> [{ id: "issue-101", status: "in_progress" }, { id: "issue-99", status: "todo" }]
GET /api/companies/company-1/issues?assigneeAgentId=agent-42&status=todo,in_progress,in_review,blocked
# -> [{ id: "issue-101", status: "in_progress" }, { id: "issue-100", status: "in_review" }, { id: "issue-99", status: "todo" }]
# Continue in_progress work
GET /api/issues/issue-101
@@ -96,7 +96,7 @@ PATCH /api/issues/issue-101
# Pick up next task
POST /api/issues/issue-99/checkout
{ "agentId": "agent-42", "expectedStatuses": ["todo"] }
{ "agentId": "agent-42", "expectedStatuses": ["todo", "backlog", "blocked", "in_review"] }
# Partial progress
PATCH /api/issues/issue-99
@@ -401,15 +401,15 @@ function buildWakeText(
"1) GET /api/agents/me",
`2) Determine issueId: PAPERCLIP_TASK_ID if present, otherwise issue_id (${issueIdHint}).`,
"3) If issueId exists:",
" - POST /api/issues/{issueId}/checkout with {\"agentId\":\"$PAPERCLIP_AGENT_ID\",\"expectedStatuses\":[\"todo\",\"backlog\",\"blocked\"]}",
" - POST /api/issues/{issueId}/checkout with {\"agentId\":\"$PAPERCLIP_AGENT_ID\",\"expectedStatuses\":[\"todo\",\"backlog\",\"blocked\",\"in_review\"]}",
" - GET /api/issues/{issueId}",
" - GET /api/issues/{issueId}/comments",
" - Execute the issue instructions exactly.",
" - If instructions require a comment, POST /api/issues/{issueId}/comments with {\"body\":\"...\"}.",
" - PATCH /api/issues/{issueId} with {\"status\":\"done\",\"comment\":\"what changed and why\"}.",
"4) If issueId does not exist:",
" - GET /api/companies/$PAPERCLIP_COMPANY_ID/issues?assigneeAgentId=$PAPERCLIP_AGENT_ID&status=todo,in_progress,blocked",
" - Pick in_progress first, then todo, then blocked, then execute step 3.",
" - GET /api/companies/$PAPERCLIP_COMPANY_ID/issues?assigneeAgentId=$PAPERCLIP_AGENT_ID&status=todo,in_progress,in_review,blocked",
" - Pick in_progress first, then in_review when you were woken by a comment, then todo, then blocked, then execute step 3.",
"",
"Useful endpoints for issue work:",
"- POST /api/issues/{issueId}/comments",
@@ -24,8 +24,8 @@ If `PAPERCLIP_APPROVAL_ID` is set:
## 4. Get Assignments
- `GET /api/companies/{companyId}/issues?assigneeAgentId={your-id}&status=todo,in_progress,blocked`
- Prioritize: `in_progress` first, then `todo`. Skip `blocked` unless you can unblock it.
- `GET /api/companies/{companyId}/issues?assigneeAgentId={your-id}&status=todo,in_progress,in_review,blocked`
- Prioritize: `in_progress` first, then `in_review` when you were woken by a comment on it, then `todo`. Skip `blocked` unless you can unblock it.
- If there is already an active run on an `in_progress` task, just move on to the next thing.
- If `PAPERCLIP_TASK_ID` is set and assigned to you, prioritize that task.
+2
View File
@@ -1893,6 +1893,7 @@ export function issueRoutes(
issueId: currentIssue.id,
taskId: currentIssue.id,
commentId: comment.id,
wakeCommentId: comment.id,
source: "issue.comment.reopen",
wakeReason: "issue_reopened_via_comment",
reopenedFrom: reopenFromStatus,
@@ -1916,6 +1917,7 @@ export function issueRoutes(
issueId: currentIssue.id,
taskId: currentIssue.id,
commentId: comment.id,
wakeCommentId: comment.id,
source: "issue.comment",
wakeReason: "issue_commented",
...(interruptedRunId ? { interruptedRunId } : {}),
+6 -5
View File
@@ -38,12 +38,13 @@ Follow these steps every time you wake up:
- add a markdown comment explaining why it remains open and what happens next.
Always include links to the approval and issue in that comment.
**Step 3 — Get assignments.** Prefer `GET /api/agents/me/inbox-lite` for the normal heartbeat inbox. It returns the compact assignment list you need for prioritization. Fall back to `GET /api/companies/{companyId}/issues?assigneeAgentId={your-agent-id}&status=todo,in_progress,blocked` only when you need the full issue objects.
**Step 3 — Get assignments.** Prefer `GET /api/agents/me/inbox-lite` for the normal heartbeat inbox. It returns the compact assignment list you need for prioritization. Fall back to `GET /api/companies/{companyId}/issues?assigneeAgentId={your-agent-id}&status=todo,in_progress,in_review,blocked` only when you need the full issue objects.
**Step 4 — Pick work (with mention exception).** Work on `in_progress` first, then `todo`. Skip `blocked` unless you can unblock it.
**Step 4 — Pick work (with mention exception).** Work on `in_progress` first, then `in_review` (if you were woken by a comment on it — check `PAPERCLIP_WAKE_COMMENT_ID`), then `todo`. Skip `blocked` unless you can unblock it.
**Blocked-task dedup:** Before working on a `blocked` task, fetch its comment thread. If your most recent comment was a blocked-status update AND no new comments from other agents or users have been posted since, skip the task entirely — do not checkout, do not post another comment. Exit the heartbeat (or move to the next task) instead. Only re-engage with a blocked task when new context exists (a new comment, status change, or event-based wake like `PAPERCLIP_WAKE_COMMENT_ID`).
If `PAPERCLIP_TASK_ID` is set and that task is assigned to you, prioritize it first for this heartbeat.
If this run was triggered by a comment mention (`PAPERCLIP_WAKE_COMMENT_ID` set; typically `PAPERCLIP_WAKE_REASON=issue_comment_mentioned`), you MUST read that comment thread first, even if the task is not currently assigned to you.
If this run was triggered by a comment on a task you own (`PAPERCLIP_WAKE_COMMENT_ID` set; `PAPERCLIP_WAKE_REASON=issue_commented`), you MUST read that comment, then checkout and address the feedback. This includes `in_review` tasks — if someone comments with feedback, re-checkout the task to address it.
If this run was triggered by a comment mention (`PAPERCLIP_WAKE_COMMENT_ID` set; `PAPERCLIP_WAKE_REASON=issue_comment_mentioned`), you MUST read that comment thread first, even if the task is not currently assigned to you.
If that mentioned comment explicitly asks you to take the task, you may self-assign by checking out `PAPERCLIP_TASK_ID` as yourself, then proceed normally.
If the comment asks for input/review but not ownership, respond in comments if useful, then continue with assigned work.
If the comment does not direct you to take ownership, do not self-assign.
@@ -54,7 +55,7 @@ If nothing is assigned and there is no valid mention-based ownership handoff, ex
```
POST /api/issues/{issueId}/checkout
Headers: Authorization: Bearer $PAPERCLIP_API_KEY, X-Paperclip-Run-Id: $PAPERCLIP_RUN_ID
{ "agentId": "{your-agent-id}", "expectedStatuses": ["todo", "backlog", "blocked"] }
{ "agentId": "{your-agent-id}", "expectedStatuses": ["todo", "backlog", "blocked", "in_review"] }
```
If already checked out by you, returns normally. If owned by another agent: `409 Conflict` — stop, pick a different task. **Never retry a 409.**
@@ -314,7 +315,7 @@ PATCH /api/agents/{agentId}/instructions-path
| My identity | `GET /api/agents/me` |
| My compact inbox | `GET /api/agents/me/inbox-lite` |
| Report a user's Mine inbox view | `GET /api/agents/me/inbox/mine?userId=:userId` |
| My assignments | `GET /api/companies/:companyId/issues?assigneeAgentId=:id&status=todo,in_progress,blocked` |
| My assignments | `GET /api/companies/:companyId/issues?assigneeAgentId=:id&status=todo,in_progress,in_review,blocked` |
| Checkout task | `POST /api/issues/:issueId/checkout` |
| Get task + ancestors | `GET /api/issues/:issueId` |
| List issue documents | `GET /api/issues/:issueId/documents` |
+3 -3
View File
@@ -203,7 +203,7 @@ GET /api/agents/me
-> { id: "agent-42", companyId: "company-1", ... }
# 2. Check inbox
GET /api/companies/company-1/issues?assigneeAgentId=agent-42&status=todo,in_progress,blocked
GET /api/companies/company-1/issues?assigneeAgentId=agent-42&status=todo,in_progress,in_review,blocked
-> [
{ id: "issue-101", title: "Fix rate limiter bug", status: "in_progress", priority: "high" },
{ id: "issue-99", title: "Implement login API", status: "todo", priority: "medium" }
@@ -224,7 +224,7 @@ PATCH /api/issues/issue-101
# 6. Still have time. Checkout the next task.
POST /api/issues/issue-99/checkout
{ "agentId": "agent-42", "expectedStatuses": ["todo"] }
{ "agentId": "agent-42", "expectedStatuses": ["todo", "backlog", "blocked", "in_review"] }
GET /api/issues/issue-99
-> { ..., ancestors: [{ title: "Build auth system", ... }] }
@@ -291,7 +291,7 @@ GET /api/companies/company-1/issues?assigneeAgentId=mgr-1&status=todo,in_progres
-> [ { id: "issue-30", title: "Break down Q2 roadmap into tasks", status: "todo" } ]
POST /api/issues/issue-30/checkout
{ "agentId": "mgr-1", "expectedStatuses": ["todo"] }
{ "agentId": "mgr-1", "expectedStatuses": ["todo", "backlog", "blocked", "in_review"] }
# 6. Create subtasks and delegate.
POST /api/companies/company-1/issues
+1 -1
View File
@@ -75,7 +75,7 @@ export const issuesApi = {
checkout: (id: string, agentId: string) =>
api.post<Issue>(`/issues/${id}/checkout`, {
agentId,
expectedStatuses: ["todo", "backlog", "blocked"],
expectedStatuses: ["todo", "backlog", "blocked", "in_review"],
}),
release: (id: string) => api.post<Issue>(`/issues/${id}/release`, {}),
listComments: (id: string) => api.get<IssueComment[]>(`/issues/${id}/comments`),