diff --git a/server/src/routes/issues.ts b/server/src/routes/issues.ts index e997d0b3..121af68d 100644 --- a/server/src/routes/issues.ts +++ b/server/src/routes/issues.ts @@ -282,11 +282,18 @@ export function issueRoutes(db: Db, storage: StorageService) { heartbeat.wakeup(mentionedId, { source: "automation", triggerDetail: "system", - reason: `Mentioned in comment on issue ${id}`, + reason: "issue_comment_mentioned", payload: { issueId: id, commentId: comment!.id }, requestedByActorType: actor.actorType, requestedByActorId: actor.actorId, - contextSnapshot: { issueId: id, commentId: comment!.id, source: "comment.mention" }, + contextSnapshot: { + issueId: id, + taskId: id, + commentId: comment!.id, + wakeCommentId: comment!.id, + wakeReason: "issue_comment_mentioned", + source: "comment.mention", + }, }).catch((err) => logger.warn({ err, agentId: mentionedId }, "failed to wake mentioned agent")); } }).catch((err) => logger.warn({ err, issueId: id }, "failed to resolve @-mentions")); @@ -504,11 +511,18 @@ export function issueRoutes(db: Db, storage: StorageService) { heartbeat.wakeup(mentionedId, { source: "automation", triggerDetail: "system", - reason: `Mentioned in comment on issue ${id}`, + reason: "issue_comment_mentioned", payload: { issueId: id, commentId: comment.id }, requestedByActorType: actor.actorType, requestedByActorId: actor.actorId, - contextSnapshot: { issueId: id, commentId: comment.id, source: "comment.mention" }, + contextSnapshot: { + issueId: id, + taskId: id, + commentId: comment.id, + wakeCommentId: comment.id, + wakeReason: "issue_comment_mentioned", + source: "comment.mention", + }, }).catch((err) => logger.warn({ err, agentId: mentionedId }, "failed to wake mentioned agent")); } }).catch((err) => logger.warn({ err, issueId: id }, "failed to resolve @-mentions")); diff --git a/skills/paperclip/SKILL.md b/skills/paperclip/SKILL.md index abb41006..06fde062 100644 --- a/skills/paperclip/SKILL.md +++ b/skills/paperclip/SKILL.md @@ -35,9 +35,13 @@ Follow these steps every time you wake up: **Step 3 — Get assignments.** `GET /api/companies/{companyId}/issues?assigneeAgentId={your-agent-id}&status=todo,in_progress,blocked`. Results sorted by priority. This is your inbox. -**Step 4 — Pick work.** Work on `in_progress` first, then `todo`. Skip `blocked` unless you can unblock it. **If nothing is assigned, exit the heartbeat. Do not look for unassigned work. Do not self-assign.** +**Step 4 — Pick work (with mention exception).** Work on `in_progress` first, then `todo`. Skip `blocked` unless you can unblock it. If `PAPERCLIP_TASK_ID` is set and that task is assigned to you, prioritize it first for this heartbeat. -If `PAPERCLIP_WAKE_REASON` indicates a comment-triggered wake (for example `issue_commented` or `issue_reopened_via_comment`), prioritize understanding and responding to that comment before other work on the same issue. +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 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. +If nothing is assigned and there is no valid mention-based ownership handoff, exit the heartbeat. **Step 5 — Checkout.** You MUST checkout before doing any work. Include the run ID header: @@ -70,7 +74,8 @@ Status values: `backlog`, `todo`, `in_progress`, `in_review`, `done`, `blocked`, - **Always checkout** before working. Never PATCH to `in_progress` manually. - **Never retry a 409.** The task belongs to someone else. -- **Never self-assign** or look for unassigned work. No assignments = exit. +- **Never look for unassigned work.** +- **Self-assign only for explicit @-mention handoff.** This requires a mention-triggered wake with `PAPERCLIP_WAKE_COMMENT_ID` and a comment that clearly directs you to do the task. Use checkout (never direct assignee patch). Otherwise, no assignments = exit. - **Always comment** on `in_progress` work before exiting a heartbeat. - **Always set `parentId`** on subtasks (and `goalId` unless you're CEO/manager creating top-level work). - **Never cancel cross-team tasks.** Reassign to your manager with a comment. diff --git a/skills/paperclip/references/api-reference.md b/skills/paperclip/references/api-reference.md index 7cc91823..da522393 100644 --- a/skills/paperclip/references/api-reference.md +++ b/skills/paperclip/references/api-reference.md @@ -214,9 +214,14 @@ The name must match the agent's `name` field exactly (case-insensitive). This tr **Do NOT:** -- Use @-mentions as a substitute for task assignment. If you need someone to do work, create a task. +- Use @-mentions as your default assignment mechanism. If you need someone to do work, create/assign a task. - Mention agents unnecessarily. Each mention triggers a heartbeat that costs budget. +**Exception (handoff-by-mention):** + +- If an agent is explicitly @-mentioned with a clear directive to take the task, that agent may read the thread and self-assign via checkout for that issue. +- This is a narrow fallback for missed assignment flow, not a replacement for normal assignment discipline. + --- ## Cross-Team Work and Delegation @@ -417,7 +422,7 @@ Terminal states: `done`, `cancelled` | ------------------------------------------- | ----------------------------------------------------- | ------------------------------------------------------- | | Start work without checkout | Another agent may claim it simultaneously | Always `POST /issues/:id/checkout` first | | Retry a `409` checkout | The task belongs to someone else | Pick a different task | -| Look for unassigned work | You're overstepping; managers assign work | If you have no assignments, exit the heartbeat | +| Look for unassigned work | You're overstepping; managers assign work | If you have no assignments, exit, except explicit mention handoff | | Exit without commenting on in-progress work | Your manager can't see progress; work appears stalled | Leave a comment explaining where you are | | Create tasks without `parentId` | Breaks the task hierarchy; work becomes untraceable | Link every subtask to its parent | | Cancel cross-team tasks | Only the assigning team's manager can cancel | Reassign to your manager with a comment |