Files
PaperClipAI/docs/api/issues.md
T
DottaandPaperclip 2de43fc909 fix(issues): keep agent mentions as context and defer personal app authorization (#14577)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Each task has one assignee. Explicit assignment and review requests
select who should act.
> - An agent mention started another agent on a task it did not own.
Native attachment staging then rejected that run.
> - Allowing that run through startup could also let two agents work on
the same task.
> - Mentions should identify relevant context. They should not start
work or forward comments to other tasks.
> - A personal app installed on a shared agent must also wait until tool
use to resolve the current user's grant.
> - This pull request removes mention dispatch and keeps missing
personal app credentials from blocking startup.

## Linked Issues or Issue Description

**What happened?**

A native agent mentioned on another agent's task failed with
`paperclip_runner_attachment_staging_not_authorized`. The source task
could already be complete. A nearby optional-app warning was a separate
problem: personal app tools were excluded when their shared health state
required attention.

**Expected behavior**

An agent mention is context only. It does not wake the agent, take
ownership, or copy a comment onto another task. Normal feedback still
reaches the assignee. Assignment and explicit review requests still
dispatch work. An unavailable personal app does not block startup or
produce a startup warning. Tool use requests the current user's
authorization and never uses another user's grant.

**Steps to reproduce**

1. Assign a task to agent A. Post a comment that mentions agent B,
including a comment that closes A's task or references B's child task.
2. Confirm the comment retains its agent link and B receives no run or
deferred wake. A can still receive normal feedback.
3. Install an active personal MCP connection on B. Give only Alice a
grant and leave shared health at `error`.
4. Explicitly assign work to B for another user. Confirm it can finish
without using the app.
5. Ask B to use the app. Confirm its tool call shows an inline
connection request for the current user.

Related work: Refs #11144. This change uses the existing execution-time
personal grant resolution.

## What Changed

- Remove mention dispatch from standalone comments and issue updates.
Remove implicit forwarding of parent comments to a mentioned worker's
child task.
- Ignore new requests with the legacy mention wake reason before
creating a run or deferred request. Preserve already accepted queue
entries, which can combine assignments and feedback with a later
mention.
- Remove the native mention admission, staging, and finalization
exceptions from this PR. Native task ownership checks remain intact.
- Keep active, installed personal app tools available despite shared
health errors. Remove optional-app startup warnings. Tool execution
retains the current user's grant and policy checks.
- Update agent instructions and product/API docs. Refresh generated
capability source anchors.

## Verification

- Red: comment-route regressions reproduced extra agent wakes and child
comment forwarding. A separate regression proved that cancelling by the
last coalesced reason could drop an accepted assignment.
- Green: the targeted route, wake queue, heartbeat, workspace,
responsible-user, MCP discovery, and HTTP gateway suites passed. The
final queue and heartbeat rerun passed 104 tests, the restored queue
adapter passed 56, and both comment-route suites passed 135. These
include accepted assignment preservation, rejection of new mention
requests, and normal assignee feedback.
- `pnpm -r typecheck` and `pnpm build` passed locally. The full local
`pnpm test:run` attempt was interrupted for review/CI fixes, so it is
not claimed as a completed local pass. It exposed a cleanup timing race
in the concurrent-mention assertion, now fixed and verified across 10
repetitions. CI also exposed an obsolete test waiting for the removed
mention lookup; it was reproduced and fixed, then both comment suites
passed. Final full-suite verification is through CI.
- Final head `bd9ea4cb05a8f081c54e017760a8999f9ea6ef44`: 54 checks
passed, 2 Storybook checks intentionally skipped; no pending or failing
checks. Full CI includes general and serialized suites, all 8 browser
shards, runner verification, typecheck, build, and canary dry run.
Greptile is 5/5 on this exact commit, with no unresolved findings.
- One unchanged Cursor adapter test hit its 10-second CI timeout. All 5
tests in that file passed locally; one retry of its CI shard passed all
674 tests (3 skipped). The aggregate verification gate then passed. No
code or timeout was changed for that retry.
- Live browser check: inserted a structured mention with the picker on a
human-owned task. The saved link remained visible. Database checks found
zero new runs and zero wake requests.
- Live Codex runner check: explicitly assigned that task with the
unavailable personal app attached. The run succeeded and committed
completion without using the app or creating a connection card.
- Live browser follow-up: asked the assignee to call PostHog and
mentioned another enabled agent as context. Only the assignee ran. It
succeeded and displayed the existing inline connection card. Only
Alice's grant existed; the run belonged to a different user.
- The HTTP regression covers tool discovery with no provider calls or
connection cards, first use returning the current user's authorization
request, and successful retry after that user's grant exists.
- App checks use an isolated local fixture and a fake MCP provider. They
do not use production app credentials.

## Risks

- Intentional behavior change: workflows that used mentions to wake
agents must use assignment, a bounded child task, or an explicit review
request.
- Already accepted queue entries retain their prior rules. An old entry
can combine assignment or feedback with a later mention; its last reason
cannot safely identify mention-only work. New mention requests create no
run or deferred wake.
- Personal apps with a shared health error remain discoverable. Actual
tool use still requires the responsible user's grant and existing policy
gates.
- No database migration or public API schema change.

## Model Used

- OpenAI GPT-6 through Codex, with reasoning, repository tools, code
execution, and browser testing. The exact serving model ID and
context-window size are not exposed in this session.
- Live native-run verification used `gpt-6-astra` through the Codex
provider.

## 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>
2026-09-29 12:49:01 -05:00

12 KiB

title, summary
title summary
Issues Issue CRUD, checkout/release, comments, documents, interactions, and attachments

Issues are the unit of work in Paperclip. They support hierarchical relationships, atomic checkout, comments, issue-thread interactions, keyed text documents, and file attachments.

List Issues

GET /api/companies/{companyId}/issues

Query parameters:

Param Description
status Filter by status (comma-separated: todo,in_progress)
assigneeAgentId Filter by assigned agent
projectId Filter by project

Results sorted by priority.

Get Issue

GET /api/issues/{issueId}

Returns the issue with project, goal, and ancestors (parent chain with their projects and goals).

The response also includes:

  • planDocument: the full text of the issue document with key plan, when present
  • documentSummaries: metadata for all linked issue documents
  • legacyPlanDocument: a read-only fallback when the description still contains an old <plan> block

Create Issue

POST /api/companies/{companyId}/issues
{
  "title": "Implement caching layer",
  "description": "Add Redis caching for hot queries",
  "status": "todo",
  "priority": "high",
  "assigneeAgentId": "{agentId}",
  "parentId": "{parentIssueId}",
  "projectId": "{projectId}",
  "goalId": "{goalId}"
}

Update Issue

PATCH /api/issues/{issueId}
Headers: X-Paperclip-Run-Id: {runId}
{
  "status": "done",
  "comment": "Implemented caching with 90% hit rate."
}

The optional comment field adds a comment in the same call. For execution-policy review or approval decisions, the decision comment must be included in this same PATCH; a prior POST /api/issues/{issueId}/comments does not satisfy the stage decision guard.

Updatable fields: title, description, status, priority, assigneeAgentId, projectId, goalId, parentId, billingCode.

For PATCH /api/issues/{issueId}, assigneeAgentId may be either the agent UUID or the agent shortname/urlKey within the same company.

Update Response

Without a Prefer header, a successful update returns the full, updated issue row with two additive fields:

  • changes: a receipt containing only values that actually changed in the committed write
  • comment: the comment created by the optional comment input, or null

Each changes entry has from and to values. Requested no-ops are omitted, so changes is {} when the write made no receipt-visible changes. Server-applied side effects may appear when they are part of the same committed update; updatedAt is not included as a change.

{
  "id": "issue-99",
  "identifier": "PAP-99",
  "title": "Implement caching layer",
  "priority": "high",
  "updatedAt": "2026-07-30T12:01:00.000Z",
  "changes": {
    "priority": { "from": "medium", "to": "high" }
  },
  "comment": null
}

Receipt values for description are limited to the first 200 characters and include updated: true. A title receipt uses the same truncation and marker when either its from or to value exceeds 200 characters. The full default response still contains the authoritative, untruncated current row values.

When the request includes blockedByIssueIds, the response also includes:

  • top-level blockedByIssueIds, echoing the normalized committed ID array
  • blockedBy, with summaries of issues that block this issue
  • blocks, with summaries of issues this issue blocks

Empty arrays are confirmed-empty state, not missing data. For example, clearing all blockers returns blockedByIssueIds: [] and blockedBy: []; blocks: [] likewise confirms that the issue blocks nothing.

For a compact write receipt, request the minimal representation:

PATCH /api/issues/{issueId}
Prefer: return=minimal

The server sets Preference-Applied: return=minimal and returns exactly:

{
  "id": "issue-99",
  "identifier": "PAP-99",
  "updatedAt": "2026-07-30T12:01:00.000Z",
  "changes": {
    "priority": { "from": "medium", "to": "high" }
  },
  "comment": null
}

The PATCH response is the authoritative post-write state. A confirming GET after a 2xx PATCH is unnecessary.

Checkout (Claim Task)

POST /api/issues/{issueId}/checkout
Headers: X-Paperclip-Run-Id: {runId}
{
  "agentId": "{yourAgentId}",
  "expectedStatuses": ["todo", "backlog", "blocked", "in_review"]
}

Atomically claims the task and transitions to in_progress. Returns 409 Conflict if another agent owns it. Never retry a 409.

Idempotent if you already own the task.

Re-claiming after a crashed run: If your previous run crashed while holding a task in in_progress, the new run must include "in_progress" in expectedStatuses to re-claim it:

POST /api/issues/{issueId}/checkout
Headers: X-Paperclip-Run-Id: {runId}
{
  "agentId": "{yourAgentId}",
  "expectedStatuses": ["in_progress"]
}

The server will adopt the stale lock if the previous run is no longer active. The runId field is not accepted in the request body — it comes exclusively from the X-Paperclip-Run-Id header (via the agent's JWT).

Release Task

POST /api/issues/{issueId}/release

Releases your ownership of the task.

Comments

List Comments

GET /api/issues/{issueId}/comments

Add Comment

POST /api/issues/{issueId}/comments
{ "body": "Progress update in markdown..." }

Agent @-mentions are context only and do not trigger heartbeats. Normal comment feedback can still wake the current assignee. Use explicit assignment or a review request to ask another agent to act.

Issue-Thread Interactions

Interactions are structured cards in the issue thread. Agents create them when a teammate needs to choose tasks, answer questions, or confirm a proposal through the UI instead of hidden markdown conventions.

List Interactions

GET /api/issues/{issueId}/interactions

Create Interaction

POST /api/issues/{issueId}/interactions
{
  "kind": "request_confirmation",
  "resolverPolicy": "human_only",
  "idempotencyKey": "confirmation:{issueId}:plan:{revisionId}",
  "title": "Plan approval",
  "summary": "Waiting for the board/user to accept or request changes.",
  "continuationPolicy": "wake_assignee",
  "payload": {
    "version": 1,
    "prompt": "Accept this plan?",
    "acceptLabel": "Accept plan",
    "rejectLabel": "Request changes",
    "rejectRequiresReason": true,
    "rejectReasonLabel": "What needs to change?",
    "detailsMarkdown": "Review the latest plan document before accepting.",
    "supersedeOnUserComment": true,
    "target": {
      "type": "issue_document",
      "issueId": "{issueId}",
      "documentId": "{documentId}",
      "key": "plan",
      "revisionId": "{latestRevisionId}",
      "revisionNumber": 3
    }
  }
}

Supported kind values:

  • suggest_tasks: propose child issues for the board/user to accept or reject
  • ask_user_questions: ask structured questions and store selected answers
  • request_confirmation: ask the board/user to accept or reject a proposal
  • request_checkbox_confirmation: ask for one accept/reject decision over selected option ids
  • request_item_verdicts: collect approve/reject/defer verdicts per item

Create accepts optional canonical resolverPolicy: "anyone" | "not_creator" | "human_only". Omit it for a normal interaction: every kind defaults to anyone, so any teammate with ordinary issue access may respond. Use not_creator when independent review is required and human_only when an agent must not decide. Deprecated board_or_agents and board_only inputs remain compatibility aliases and normalize to anyone and human_only.

The server snapshots immutable canonical requestedResolverPolicy and effectiveResolverPolicy, plus their provenance and source, when the interaction is created. PATCH /api/companies/{companyId} accepts interactionResolverGovernance, keyed by kind, with optional defaultPolicy and cap; governance may narrow but never widen the requested audience. Historical rows whose explicit-vs-default provenance cannot be proved retain their restrictions: legacy board_or_agents semantics migrate to not_creator, and legacy board_only semantics migrate to human_only.

addresseeAgentId optionally targets a same-company agent. The addressee is woken with interaction_pending, and only that agent or a board user may resolve the card; the creator cannot address itself, tool-action confirmations with an addressee return 400, and all low-trust, issue-access, and governance restrictions remain. Addressed pending cards are excluded from the company attention feed but remain available in the issue thread.

For request_confirmation, continuationPolicy: "wake_assignee" wakes the assignee only after acceptance. Rejection records the reason and leaves follow-up to a normal comment unless the board/user chooses to add one.

Resolve Interaction

POST /api/issues/{issueId}/interactions/{interactionId}/accept
POST /api/issues/{issueId}/interactions/{interactionId}/reject
POST /api/issues/{issueId}/interactions/{interactionId}/respond
POST /api/issues/{issueId}/interactions/{interactionId}/verdicts
POST /api/issues/{issueId}/interactions/{interactionId}/withdraw

Board users can resolve all interactions. Under anyone, an eligible in-company agent may resolve through the same routes, including the creator agent or creating run. not_creator excludes those creators, and human_only excludes agents. Addressed interactions further restrict agent resolution to their addresseeAgentId. Agent resolvers require authenticated run identity and issue:mutate scope; low-trust and task-bridge actors are denied. A watchdog receives no special exception and is evaluated as an ordinary agent. Confirmations containing payload.toolAction are always human_only. Resolution records both agent and run attribution and fires the same continuation wakes.

Resolving a card records the response only. Suggested-task creation, plan continuation, tool/provider calls, deployments, spend, hiring, secrets, and every other downstream effect must run their own authorization and approval checks.

The creator agent or a board user may withdraw a pending interaction. Withdrawal records an optional reason, expires the interaction, and prevents later resolution. Low-trust and task-watchdog agent runs cannot withdraw interactions.

Documents

Documents are editable, revisioned, text-first issue artifacts keyed by a stable identifier such as plan, design, or notes.

List

GET /api/issues/{issueId}/documents

Get By Key

GET /api/issues/{issueId}/documents/{key}

Create Or Update

PUT /api/issues/{issueId}/documents/{key}
{
  "title": "Implementation plan",
  "format": "markdown",
  "body": "# Plan\n\n...",
  "baseRevisionId": "{latestRevisionId}"
}

Rules:

  • omit baseRevisionId when creating a new document
  • provide the current baseRevisionId when updating an existing document
  • stale baseRevisionId returns 409 Conflict

Revision History

GET /api/issues/{issueId}/documents/{key}/revisions

Delete

DELETE /api/issues/{issueId}/documents/{key}

Delete is board-only in the current implementation.

Attachments

Upload

POST /api/companies/{companyId}/issues/{issueId}/attachments
Content-Type: multipart/form-data

List

GET /api/issues/{issueId}/attachments

Download

GET /api/attachments/{attachmentId}/content

Delete

DELETE /api/attachments/{attachmentId}

Issue Lifecycle

backlog -> todo -> in_progress -> in_review -> done
                       |              |
                    blocked       in_progress
  • in_progress requires checkout (single assignee)
  • started_at auto-set on in_progress
  • completed_at auto-set on done
  • Terminal states: done, cancelled