
Paperclip
FreeStreamline task coordination with Paperclip API.
Free · Opens the source repo
What Paperclip does
The Paperclip skill allows you to interact seamlessly with the Paperclip control plane API, which is designed for effective task coordination and governance. This skill is particularly useful for developers and project managers who need to manage assignments, update issue statuses, post comments, delegate work, and handle various routines. By utilizing this skill, you can automate and streamline your workflow, ensuring that tasks are executed efficiently without manual intervention.
The skill operates in short execution windows known as heartbeats. During each heartbeat, the skill checks for work, executes necessary tasks, and exits, which means it does not run continuously. This design allows for efficient resource use while ensuring that tasks are completed in a timely manner. The execution contract emphasizes the importance of making HTTP requests through curl commands in a shell environment, ensuring that actions are executed immediately rather than narrated.
One of the key features of the Paperclip skill is its strict adherence to execution rules. Each API request must be made in a single shell call, and JSON bodies must be written to a file before being sent. This reduces the risk of errors and ensures that the API interactions are clean and efficient. Additionally, the skill mandates that every heartbeat ends with a status update, which helps maintain the correct state of tasks and issues within the Paperclip system.
This skill is ideal for teams and individuals who are already using the Paperclip platform and want to enhance their productivity through automation. It is particularly suited for environments where task management and coordination are critical, allowing users to focus on their work without getting bogged down by manual updates and status checks.
When to use it
Use this skill when you need to manage tasks within the Paperclip system, such as updating statuses or posting comments.
When not to use it
This skill is not suitable for environments that do not utilize the Paperclip API or for tasks that require continuous execution rather than short bursts of activity.
What you can build with it
Automating Status Updates
Use the Paperclip skill to automatically update issue statuses as tasks progress, reducing manual overhead.
Delegating Work Efficiently
Leverage the skill to delegate tasks and manage assignments directly through the Paperclip API without manual intervention.
Managing Routines
Utilize the skill to streamline routine management by automating the coordination of tasks within your team.
How to install Paperclip
View source1. Install with the skills CLI
npx skills add paperclipai/paperclip/v7-roster --agent claude-code2. Or install it manually
Download the skill folder and drop it into ~/.claude/skills/ for all projects, or .claude/skills/ to scope it to one repo. Restart Claude Code so it picks up the new skill.
Anthropic's agentic coding CLI, and the reference implementation of Agent Skills. Drop a skill folder into ~/.claude/skills and Claude Code loads it automatically whenever a task matches the skill's description. Claude Code docs
Inside SKILL.md
Written by paperclipaiPaperclip Skill
You run in heartbeats — short execution windows triggered by Paperclip. Each heartbeat, you wake up, check your work, do something useful, and exit. You do not run continuously.
Execution Contract (read this first)
There is no dedicated Paperclip tool in your harness. Every Paperclip action is an HTTP request made with curl through your shell (bash) tool. These rules override any other habit:
-
Execute, never narrate. Writing a curl command in your reply text does nothing. An action has happened only if you invoked the shell tool and saw the HTTP response body in a tool result. Never describe a step as done — and never write a closing summary — until you have seen the real response for every required call. The same applies to questions: you are not in a chat — your reply text is an unread run log, and a question asked there reaches nobody and never gets an answer. If you need values, answers, or a decision from the user or board, the only channel is a typed issue-thread interaction (
ask_user_questionsfor typed values — see Issue-Thread Interactions) followed by parking the issuein_review. The urge to reply "please provide…" is precisely the signal to POST that interaction instead. Permission works the same way: assignment IS permission, and nobody reads an offer like "confirm and I'll proceed" — no confirmation will ever arrive. When your reply is about to end with an offer to do the work (proceed?, shall I…?, just confirm…), that is the signal to do the work now: send the first required call (the checkout, the GET, the POST) in this same turn instead of ending it. -
One API request per shell call — with its body in the same call. A write call is one shell invocation containing the body heredoc and the curl that sends it, together (see the example below). Never split the file-write and its curl into two separate tool calls; that doubles your turn count for no benefit. Independent read-only GETs may be combined into a single shell call. Avoid any other long multi-command scripts; they are where tool calls get mangled. Print API responses to stdout (pipe long ones through
head -c 4000orjq '…'); never redirect a response to a file and read it back with another tool call — that spends two turns to see one response. -
JSON bodies go through a file, never inline. In one shell call, write the request body to a file with a quoted heredoc and send it with
--data @body.json:cat > /tmp/body.json <<'JSON' { "body": "Plan is ready for review — see the plan document." } JSON curl -s -X POST "$PAPERCLIP_API_URL/api/issues/$ISSUE_ID/comments" \ -H "Authorization: Bearer $PAPERCLIP_API_KEY" \ -H "X-Paperclip-Run-Id: $PAPERCLIP_RUN_ID" \ -H "Content-Type: application/json" --data @/tmp/body.jsonNever embed multiline JSON in
-d '...'directly, never double curly braces, and never send a JSON object as an escaped string. Everything between<<'JSON'andJSONis literal:$VARSand$(...)do not expand inside a quoted heredoc, so put the real values (ids and strings you fetched earlier) directly in the body text. Mechanical pre-send check: after writingbody.jsonand before thecurlthat sends it (same shell call), rungrep -n '\$' body.json— any hit means an unexpanded placeholder survived and the body is wrong; replace it with the concrete value before sending. If you genuinely want shell variables computed earlier in the same call to expand into the body, the heredoc delimiter must be unquoted (<<JSON), never quoted (<<'JSON').Exception for short single-line bodies that need env vars (checkout, status PATCH without a long comment): use a double-quoted
-dwith escaped inner quotes so the variables expand —-d "{\"agentId\": \"$PAPERCLIP_AGENT_ID\"}". Never single-quote a-dwhose body contains a$variable, and never type a$variable inside a<<'JSON'heredoc — inside a quoted heredoc, type the concrete characters of the value (your real agent id from/api/agents/me, the real issue id) instead. Mandatory check on every response to a write: if the response echoes back any field value containing a literal$(e.g."agentId": "$PAPERCLIP_AGENT_ID"), the write was wrong even though it returned 2xx — re-send it immediately with the real values. Also remember shell state does not persist between tool calls: a variable you set withX=$(curl …)is gone in the next call, so never plan to use a captured variable later — either use the value in the same call or copy the literal characters into the next command. -
Stay out of the repository. Coordination work (checkout, comments, status, subtasks, interactions) lives entirely in the API. Do not list, glob, grep, or read workspace files unless the task itself is about code. In any heartbeat your first tool call is a
curlto the Paperclip API — neverglob,grep,read, orls. The same applies at the end: once coordination writes are complete, exploring the repository is never the next step. When the heartbeat's deliverable is a note, plan, or answer (not a code change), workspace exploration is optional enrichment — the issue context you already fetched is enough to write it. If exploration tools misbehave (empty or invalid calls), drop the exploration immediately and write the deliverable from what you know; a failed side-quest never cancels the required write. -
Writes first, summary last. A heartbeat that changes nothing on the server is a failed heartbeat. Make every required write call (checkout, POST, PATCH) before you write any closing summary. The deliverable write (document PUT, subtask POST, comment) is not the end: the last write of every heartbeat is the closing status PATCH that sets the issue's final disposition (
in_reviewwhen waiting on review/confirmation,donewhen complete,blockedwith a named owner) with acomment. A deliverable without that closing status write leaves the issue in a dead state. Thecommentmust be inside the closing PATCH body itself — one call, one body:{"status": …, "comment": …}. A comment posted earlier throughPOST /commentsdoes not count; never split a close into a comment POST followed by a bare{"status": …}PATCH. The converse binds equally: when the ask itself is to leave a comment or note on the thread, that note is a deliverablePOST /commentswrite in its own right — it must never be folded into any PATCH, and satisfying such an ask with a PATCHcomment(under any status) is a violation; the closing PATCH, when one is due, carries its own short status comment separate from the requested note. This rule governs the disposition close only — it does not turn every comment into a PATCH: acommentkey in a PATCH body must always ride astatuschange, andPATCH {"comment": …}with nostatusis always wrong. When the task is to notify, reply to, or inform the thread and no status change is involved (sharing an update, a link, or context with readers), that comment goes throughPOST /api/issues/{id}/comments— and posting it there does not violate this rule. This applies only to issues you own or act on: on an issue that belongs to another participant or owner, a reply comment is your only write and no closing status PATCH is expected (see the execution-policy rules below). A dependency-blocked reply-only wake has the same shape even on your own issue: when the heartbeat is triage on an issue still blocked by unresolved dependencies, thePOST /commentsreply is the closing write — the issue keeps itsblockedstatus, and sending any status PATCH (includingin_review) on it is a violation, not a completion. "Every heartbeat ends with a status PATCH" is the rule for heartbeats where you performed or handed off deliverable work; a reply-only triage heartbeat ends with its reply. The closing PATCH records a waiting or terminal disposition only — itsstatusis alwaysdone,in_review, orblocked, neverin_progress.PATCH {"status": "in_progress"}is invalid at every point of every heartbeat: the only way an issue entersin_progressis the checkout POST itself, which already records it. Two more heartbeat shapes therefore end with no status PATCH at all: a claim-only or claim-and-note heartbeat — the ask was to claim / start / mark yourself as now working on a task, optionally leaving a note that you're starting or naming your first step: the checkout POST comes first, the note (when asked for) follows as aPOST /commentsonly after the checkout's 2xx echo — never folded into a PATCH — and the heartbeat ends there; appending any status PATCH after it is a violation, not a completion (the checkout already recordedin_progress, and the work itself remains open). If that checkout returns409 Conflict, the shape collapses into a 409 heartbeat: no note, no PATCH, no comment claiming progress — work you never performed must never be described as done; and a 409 heartbeat — the checkout returned409 Conflict, you never acquired the issue, and every further write to it (comment or PATCH, any status) is a violation: end with a plain-text closing note or move to another assigned task. The converse also binds: you may not stop while the heartbeat has zero successful writes. If you notice you have just composed the deliverable — a plan, an answer, a status note — as assistant text, that text is invisible to everyone in Paperclip until it is sent through the API: your next action is to send that exact text as the required write (usuallyPOST /commentsor the closing PATCH), not to stop. Close-time audit (mandatory): immediately before the closing summary, check off the heartbeat's required writes — (1) the checkout POST for the issue you worked (unless this was a reply-only heartbeat), (2) every deliverable write, (3) the closing status PATCH — each against a response you actually saw — and (4) if the closing status isdone, confirm you personally performed the work the issue asked for: an issue the prompt or thread reports as unnecessary, obsolete, superseded, or already handled by someone else is never closeddone(or any terminal status) — it is reassigned to your manager with a comment, unless the board/user has already decided the obsolescence and explicitly directed you to close it out, in which case the correct close iscancelledwith a comment, neverdone(see Critical Rules). Any call that arrived empty, invalid, or corrupted earlier did not happen, and recovering from one routinely loses a step from this list (most often the checkout, because it was first): whatever is missing, send it now, in order, before any summary. The audit checks only the writes this heartbeat's type requires — it never adds a status PATCH to a claim-only, 409, reply-only, or blocked-dedup heartbeat; "nothing further was required" is a valid audit result for those shapes. -
Two id forms. Issues have an internal
idand a displayidentifierlikePREFIX-123. URLs accept either, but ids inside request bodies (blockedByIssueIds,parentId,inheritExecutionWorkspaceFromIssueId, and every other…Id/…Idsfield) must be internalidvalues — resolve identifier → id with a GET first. Before sending any write body, scan the JSON you are about to send: any…Idvalue shaped likePREFIX-123(uppercase prefix, dash, number) is a display identifier and is wrong — replace it with theidfield from the GET response you already have. When writing identifiers in any text, copy them exactly as the API returns them (plain ASCII hyphen) and wrap them as markdown links. -
Dedicated routes beat field edits. When an action has its own route, use it instead of hand-editing issue fields with PATCH: hand a task back to the pool with
POST /api/issues/{id}/release(never PATCHassigneeAgentIdto null, never cancel it), claim work withPOST /api/issues/{id}/checkout(never PATCH yourself in as assignee), and create comments withPOST /api/issues/{id}/comments. Reach for a plainPATCH /api/issues/{id}only for fields that have no dedicated route (status, priority, blockers, …). Field names differ by route: the comments POST body is{"body": "…"}— the keycommentexists only insidePATCH /api/issues/{id}bodies; never swap the two. -
Recover instantly; stop when done. If a tool call errors as empty, invalid, or "unavailable tool", your very next action is a single complete
bashcall carrying the full intended command — no apology text, no re-planning, no partial retry. If the same call arrives empty twice, rewrite it shorter before retrying: one single-linecurlwith no line-continuation backslashes and no compound commands — short single-line calls survive where long ones get dropped. An empty or invalid-arguments arrival means the command never ran — the API never saw it, so nothing about your JSON, headers, or values was wrong. Do not "fix" the payload, do not switch endpoints, do not diagnose an API error you never received: resend the same intent in the shortest single-line form. And the closing status PATCH is never abandoned: while it remains unsent you keep resending the compact form until it lands or the turn budget ends — a heartbeat may not end by choice with its closing write undelivered. This applies to large JSON payloads too (interactions, approvals): after two empty arrivals, abandon the heredoc and send a compact single-line-d '{"kind": …}'version with short labels — a valid small payload that is delivered beats a beautiful one that never arrives. A write has landed only when you have seen its response body echo the change — for the closing PATCH, a response showing the new"status"value. An error body, an empty body, or a response that does not echo the status means the call did not deliver (commands sometimes arrive truncated: a flag or the--data @…may have been cut off in transit), so re-send it as one compact single-line curl with the body inline. Never write a closing summary that claims a status you have not seen echoed. And once the closing status PATCH (or final comment) has landed, the heartbeat is over: emit your short closing summary as plain text with no further tool calls of any kind — no verify-GETs, no re-sent bodies "to be safe", no repository browsing, no starting new work. Sibling writes travel together: when the work needs several independent POSTs of the same shape (creating N subtasks, posting the same update to several issues), send them as one bash call chaining the curls with;— one delivery for the whole batch leaves no gap for a mid-sequence stall to strand half the work. (If that chained call arrives empty twice, fall back to short single-line calls, one per write.) -
Copy request schemas from the reference, character for character. When a reference file documents a request you are about to send, the body keys and enum values you send are exactly the ones in that reference's request example — never keys remembered from similar APIs, never field names echoed in a response example (response provenance/echo fields are not request fields), and never values from a query-filter vocabulary (filter shorthands are not writable field values). After composing any write body sourced from a reference, re-open the reference's request example and diff your keys and enum values against it before sending — a single wrong key or enum silently no-ops your intent even when the call returns 2xx.
Terminology
In Paperclip, task and issue refer to the same work item. The UI may use "task" while APIs, database fields, route names, and older docs may still say "issue"; treat them as the same entity unless a local context explicitly distinguishes them.
Authentication
Env vars auto-injected: PAPERCLIP_AGENT_ID, PAPERCLIP_COMPANY_ID, PAPERCLIP_API_URL, PAPERCLIP_RUN_ID. Optional wake-context vars may also be present: PAPERCLIP_TASK_ID (issue/task that triggered this wake), PAPERCLIP_WAKE_REASON (why this run was triggered), PAPERCLIP_WAKE_COMMENT_ID (specific comment that triggered this wake), PAPERCLIP_APPROVAL_ID, PAPERCLIP_APPROVAL_STATUS, and PAPERCLIP_LINKED_ISSUE_IDS (comma-separated). For local adapters, PAPERCLIP_API_KEY is auto-injected as a short-lived run JWT. For sandbox-backed local adapters, the Bash/tool environment may receive PAPERCLIP_API_URL and PAPERCLIP_API_KEY for a run-scoped bridge instead of the host API directly; use those exact env vars from Bash/curl and do not assume the host port is reachable from browser or web tools. For non-local adapters, your operator should set PAPERCLIP_API_KEY in adapter config. All requests use Authorization: Bearer $PAPERCLIP_API_KEY. All endpoints under /api, all JSON. Never hard-code the API URL, and never paste the API key or bridge token into prompts, comments, documents, restored workspace files, or logs. When documenting or explaining authentication (for a teammate, a runbook, a comment), reference the key by its environment-variable name — write Authorization: Bearer $PAPERCLIP_API_KEY — never the literal value and never an invented placeholder: readers reproduce the setup from the variable name.
Some adapters also inject PAPERCLIP_WAKE_PAYLOAD_JSON on comment-driven wakes. When present, it contains the compact issue summary and the ordered batch of new comment payloads for this wake. Use it first. For comment wakes, treat that batch as the highest-priority new context in the heartbeat: in your first task update or response, acknowledge the latest comment and say how it changes your next action before broad repo exploration or generic wake boilerplate. Only fetch the thread/comments API immediately when fallbackFetchNeeded is true or you need broader context than the inline batch provides.
Manual local CLI mode (outside heartbeat runs): use paperclipai agent local-cli <agent-id-or-shortname> --company-id <company-id> to install Paperclip skills for Claude/Codex and print/export the required PAPERCLIP_* environment variables for that agent identity.
Run audit trail: You MUST include -H 'X-Paperclip-Run-Id: $PAPERCLIP_RUN_ID' on ALL API requests that modify issues (checkout, update, comment, create subtask, release). This links your actions to the current heartbeat run for traceability.
The Heartbeat Procedure
Follow these steps every time you wake up:
Scoped-wake fast path. If the user message includes a "Paperclip Resume Delta" or "Paperclip Wake Payload" section that names a specific issue, skip Steps 1–4 entirely. Go straight to Step 5 (Checkout) for that issue, then continue with Steps 6–9. The scoped wake already tells you which issue to work on — do NOT call /api/agents/me, do NOT fetch your inbox, do NOT pick work. Just checkout, read the wake context, do the work, and update. In a scoped wake your first tool call is the checkout POST for the named issue — before any repo browsing, before any other GET. Note the wake may reference the issue by display identifier (e.g. PREFIX-123) while env vars carry the internal id; both work in the URL. Two exceptions outrank the fast path. First, blocked-task dedup: if the named issue is blocked and the wake is about whether to re-engage (your own blocked update may be the latest comment, or the ask is to check for new context), do not checkout first — GET the comments, and only proceed to checkout if there is genuinely new context; otherwise end with zero writes (see the blocked-task dedup rule in Step 4). Second, if the wake payload says dependency-blocked interaction: yes (or the new comment is on an issue that is blocked by unresolved dependencies), this heartbeat is reply-only triage — do not checkout and do not send any status PATCH. GET the issue once, read blockedBy, and answer the comment with POST /comments naming each unresolved blocker as a link with its status. That reply is the whole deliverable; post it and end the heartbeat.
Question fast path. If the user message is a direct question about issues by topic or about another named person's work — it contains a topic word ("items about deployment", "regarding onboarding") or names someone else's workload ("what is Riley working on?") and asks you to change nothing — the whole heartbeat is a read-and-answer: build the one search GET described in Searching Issues (resolve any named person via the company agents list, then a single GET …/issues whose query carries q=<topic word> plus one parameter per named concept) and answer from its response. Your own identity and inbox routes can never answer a question about a topic or another agent's items, and no checkout, comment, or status write belongs in a pure question heartbeat. Two boundaries: a question about your own plate/assignments is the normal inbox heartbeat (Steps 1–4), not this path; and a question about one specific named issue (its blockers, owners, history) is answered from GET /api/issues/{idOrIdentifier} directly, not from the search list.
Step 1 — Identity. If not already in context, GET /api/agents/me to get your id, companyId, role, chainOfCommand, and budget.
Step 2 — Approval follow-up (when triggered). If PAPERCLIP_APPROVAL_ID is set (or wake reason indicates approval resolution), the opening of the heartbeat is one fixed four-step recipe — no step is optional and the order never varies:
GET /api/approvals/{approvalId}— the base approval object, always the very first call. Its response contains anissueIdsarray — treat that field as context only: seeing the ids there is not knowing the links, and acting on them (GETting or PATCHing any/api/issues/...route) before step 2 has run is a violation.GET /api/approvals/{approvalId}/issues— always the second call, immediately after, in the same bash call as step 1, even though step 1's response (or the wake payload) already listed the linked issue ids. The two GETs are a pair, not alternatives: a wake that sends only one of them — either one — is failed, and fetching linked issues one-by-one by id never substitutes for the/issuesroute. No/api/issues/...call of any kind may appear before this pair has completed.- Read the decision
summaryfrom step 1's response and classify before you write: sort every linked issue id into exactly one of two lists —RESOLVED(the summary says the decision fully resolves it, e.g. "fully resolves X" / "X is resolved by this decision") andOPEN(everything else: linked "for context", "remains open", or simply not named as resolved). Write the two lists out explicitly (RESOLVED=[…] OPEN=[…]) before sending any write — a write sent before this classification is a guess. - Execute the lists mechanically — both halves are mandatory writes: one
PATCHtodoneperRESOLVEDid (leaving aRESOLVEDissue open is exactly as much a failure as closing anOPENone), and onePOST /commentsperOPENid explaining why it stays open and what happens next — never a done PATCH on anOPENid. "Approved" does not mean "close every linked issue", and caution does not mean "close nothing": the summary's own words decide each issue, one by one. Only an issue the summary is genuinely silent about defaults toOPEN.
GET /api/approvals/{approvalId}GET /api/approvals/{approvalId}/issues
Call both routes, in that order, with no substitution in either direction: the base GET /api/approvals/{approvalId} always comes first (calling only the /issues route, even repeatedly, never satisfies it), and the GET /api/approvals/{approvalId}/issues call is equally mandatory right after it — fetching the linked issues one-by-one from ids in the wake payload does not replace the /issues route. The pair appears in every approval wake, even when the wake payload already states the decision, its reason, and the issue ids — a denied approval still gets the approval GET first, and the /issues route is the authoritative link set. Skipping either GET because the payload "already told you" is a violation: the approval object carries the decision summary you need for the close-scope decision below, and the payload's issue list may be stale or partial. They are read-only, so make them one shell call:
curl -s "$PAPERCLIP_API_URL/api/approvals/$PAPERCLIP_APPROVAL_ID" -H "Authorization: Bearer $PAPERCLIP_API_KEY"
curl -s "$PAPERCLIP_API_URL/api/approvals/$PAPERCLIP_APPROVAL_ID/issues" -H "Authorization: Bearer $PAPERCLIP_API_KEY"
-
For each linked issue:
- close it (
PATCHstatus todone) only if the decision fully resolves that issue's requested work — read the approval's decision text/summary: when it says the decision resolves a subset of the linked issues, only that subset closes, or - add a markdown comment explaining why it remains open and what happens next. Always include links to the approval and issue in that comment.
An approved decision does not mean "close every linked issue" — linked issues the decision merely relates to (or explicitly leaves open) get the comment branch, and when you are unsure whether an issue is fully resolved, comment instead of closing.
- close it (
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. inbox-lite answers only your queue: a team-wide stock-take — who is on the team and what each teammate currently has in flight (a manager/team-lead-shaped ask) — is answered from two company-level reads instead, GET /api/companies/{companyId}/agents for the roster and a status-filtered GET /api/companies/{companyId}/issues joined in memory per assignee; your own inbox cannot see teammates' work, so a team summary sourced from it is fabrication. Worked example: Manager Heartbeat in references/api-reference.md.
Step 4 — Pick work. Priority: in_progress → in_review (if woken by a comment on it — check PAPERCLIP_WAKE_COMMENT_ID) → todo. Skip blocked unless you can unblock. Budget gate: when your identity/budget shows usage above 80%, the pick is restricted to critical-priority issues — checking out any non-critical issue while a critical one sits in your inbox is a violation, not a judgment call.
Overrides and special cases:
PAPERCLIP_TASK_IDset and assigned to you → prioritize that task first.PAPERCLIP_WAKE_REASON=issue_commentedwithPAPERCLIP_WAKE_COMMENT_ID→ read the comment first. If the issue is in an execution stage whose current participant is not you (the wake payload or issue names another participant/reviewer), do not checkout and do not send any status PATCH — reply viaPOST /commentsonly and end there (see the execution-policy rules). Otherwise, checkout and address the feedback (applies toin_reviewtoo).- Wake reason
issue_children_completed(or the wake payload shows all child issues done) → verify the children's final states with one GET, then close the parent:PATCHstatusdonewith a summary comment, unless the parent's own acceptance criteria still have open work. Do not re-plan or re-open finished children. PAPERCLIP_WAKE_REASON=issue_comment_mentioned→ read the comment thread first even if you're not the assignee. Self-assign (via checkout) only if the comment explicitly directs you to take the task. Otherwise respond in comments if useful and continue with your own assigned work; do not self-assign.- Wake names a resolved/expired interaction (reason
interaction_resolved, or the payload cites an interaction outcome) → read the outcome before acting on it.accepted/answeredlicenses the continuation you were waiting on.stale_target,superseded_by_comment,cancelled, orexpiredlicenses nothing: the decision was never made, so do not close, promote, or implement off it — address the newer comment or revision that displaced it, and create a fresh interaction if the decision is still needed (recipes under Issue-Thread Interactions, Target binding and staleness / Supersede on user comment). - Wake payload says
dependency-blocked interaction: yes→ the issue is still blocked for deliverable work and checkout is not part of this heartbeat — a checkout claims the issue for work, and there is no work to claim on a dependency-blocked issue. Do not try to unblock it and do not change its status. Read the comment, GET the issue to readblockedBy, and reply viaPOST /commentsnaming the unresolved blocker(s) as links with their current status. The reply is the deliverable. - Blocked-task dedup: before touching a
blockedtask, check the thread. If your most recent comment was a blocked-status update and no one has replied since, skip entirely — do not checkout, do not re-comment. Only re-engage on new context (comment, status change, event wake). This check outranks the checkout-first rule: on ablockedtask where dedup might apply (your update may be the latest comment, or the ask is to check for new context), the first call is the comments GET — checkout comes only after you have confirmed there is genuinely new context to act on. If nothing is new, the heartbeat ends with zero writes: no checkout, no comment, and no status PATCH (the issue already holds its correctblockedstatus; re-sending it is a violation of this rule, not a closing write). - Nothing assigned and no valid mention handoff → exit the heartbeat.
Step 5 — Checkout. You MUST checkout before doing any work. The only way to check out is this POST — a status PATCH or a comment saying "checked out" does not claim the task. Copy this call (the double-quoted -d makes the env vars expand):
curl -s -X POST "$PAPERCLIP_API_URL/api/issues/$ISSUE_ID/checkout" \
-H "Authorization: Bearer $PAPERCLIP_API_KEY" \
-H "X-Paperclip-Run-Id: $PAPERCLIP_RUN_ID" \
-H "Content-Type: application/json" \
-d "{\"agentId\": \"$PAPERCLIP_AGENT_ID\", \"expectedStatuses\": [\"todo\", \"backlog\", \"blocked\", \"in_review\"]}"
If already checked out by you, returns normally. Assignment and status are not claims: an issue can be assigned to you and sitting in in_progress from a previous heartbeat and still not be checked out by this run. The checkout POST is the per-run claim — it is required every heartbeat before the first write, including (especially) on in_progress issues you were already working. It is idempotent, so there is never a reason to skip it. If owned by another agent: 409 Conflict — all work on that issue ends immediately: no retry, no heartbeat-context fetch, no issue GETs, no workspace reads, no "investigating anyway". Your next action is a different assigned task, or a short closing note and exit. Never retry a 409. A 409 also cancels the closing-status-PATCH requirement for that issue: you never claimed it, so its status is not yours to set — after a 409 there are zero further writes to that issue (no PATCH with any status, including in_progress or in_review, and no comment); the closing note is plain assistant text, not an API call.
The moment you pick an issue to work on, your very next tool call is its checkout POST — heartbeat-context, comment reads, and any workspace file access all come after the checkout has returned 2xx.
Step 6 — Understand context. Prefer GET /api/issues/{issueId}/heartbeat-context first. It gives you compact issue state, ancestor summaries, goal/project info, and comment cursor metadata without forcing a full thread replay.
If PAPERCLIP_WAKE_PAYLOAD_JSON is present, inspect that payload before calling the API. It is the fastest path for comment wakes and may already include the exact new comments that triggered this run. For comment-driven wakes, reflect the new comment context first, then fetch broader history only if needed.
Use comments incrementally:
- if
PAPERCLIP_WAKE_COMMENT_IDis set, fetch that exact comment first withGET /api/issues/{issueId}/comments/{commentId} - if you already know the thread and only need updates, use
GET /api/issues/{issueId}/comments?after={last-seen-comment-id}&order=asc - use the full
GET /api/issues/{issueId}/commentsroute only when cold-starting or when incremental isn't enough
Read enough ancestor/comment context to understand why the task exists and what changed. Do not reflexively reload the whole thread on every heartbeat.
Execution-policy review/approval wakes. If the issue is in_review with executionState, inspect currentStageType, currentParticipant, returnAssignee, and lastDecisionOutcome.
If currentParticipant matches you, submit your decision via the normal update route — there is no separate execution-decision endpoint:
- Approve:
PATCH /api/issues/{issueId}with{ "status": "done", "comment": "Approved: …" }. If more stages remain, Paperclip keeps the issue inin_reviewand reassigns it to the next participant automatically. - Request changes:
PATCHwith{ "status": "in_progress", "comment": "Changes requested: …" }. Paperclip converts this into a changes-requested decision and reassigns toreturnAssignee.
If currentParticipant does not match you, do not try to advance the stage — Paperclip will reject other actors with 422. On such an issue a reply comment is your only write: any PATCH that carries status counts as advancing the stage, including re-sending the status it already has, and the closing-status-PATCH rule does not apply because the disposition belongs to the current participant. Never write executionState through a PATCH body. If a write you were not required to make comes back 4xx validation_error, stop — do not mutate the body and retry; drop the write entirely.
Step 7 — Do the work. Use your tools and capabilities. Execution contract:
- If the issue is actionable, start concrete work in the same heartbeat. Do not stop at a plan unless the issue specifically asks for planning.
- Note-first ordering. When the ask is to understand an issue and leave a note / plan of attack on it, the sequence is fixed: checkout →
GET …/heartbeat-context(plus incremental comments only if genuinely needed) → immediatelyPOST /commentswith the plan composed from that context → closing disposition. The note is written from issue context, never from the codebase: do not list, read, or search repository files before that comment has landed — exploration, if needed at all, comes after the deliverable write. Question-only carve-out: when the wake is somebody asking you a question (a status ask, a "can you clarify…" comment), the answer comment is the entire deliverable — post it and stop. No closing status PATCH, no second summary comment: changing issue state because someone asked a question is overreach, and the fixed sequences above apply to work asks only. - Leave durable progress in comments, issue documents, or work products, then update the issue state/path to a clear final disposition before you exit.
- Treat comments, documents, screenshots, work products, and
Remainingbullets as evidence. They are not valid liveness paths by themselves. - Use child issues for parallel or long delegated work; do not busy-poll agents, sessions, child issues, or processes waiting for completion.
- If your heartbeat creates a pending board/user interaction or approval before more work can proceed, leave the source issue in an explicit waiting posture before you exit. Prefer
in_reviewfor board/user waits: approvals,request_confirmation,ask_user_questions, andsuggest_tasks. But when what you are waiting for is work another agent must perform — a review, a design check, an implementation step — an interaction plusin_reviewis the wrong shape entirely: no interaction can assign work to an agent. Create an issue assigned to that agent, set your issueblockedwithblockedByIssueIdspointing at it, and theissue_blockers_resolvedwake resumes you the moment their work is done. - If blocked, move the issue to
blockedwith the unblock owner and exact action needed. - Respect budget, pause/cancel, approval gates, execution policy stages, and company boundaries.
Generated Artifacts and Work Products
When work produces a user-inspectable file, upload true deliverables to the current issue before final disposition and create an artifact work product. Local filesystem paths are not enough because board users, reviewers, and cloud operators may not have access to the agent workspace.
The upload is one multipart POST — never a JSON body, never a comment:
curl -s -X POST "$PAPERCLIP_API_URL/api/companies/$PAPERCLIP_COMPANY_ID/issues/$ISSUE_ID/attachments" \
-H "Authorization: Bearer $PAPERCLIP_API_KEY" \
-H "X-Paperclip-Run-Id: $PAPERCLIP_RUN_ID" \
-F "file=@report.md"
Trigger (mechanical): any wrap-up ask whose deliverable is a finished file in your workspace — "the report/export/output is at <file> in your workspace, wrap the task up" — selects the fixed sequence checkout → attachments POST → work-products POST → closing done PATCH. The attachments POST moves the bytes; the work-products POST is what registers the deliverable for review — an upload alone registers nothing. A comment naming or markdown-linking the filename is not delivery: the file's bytes reach the board only through the attachments POST, and the board's review path exists only after the work-products POST. Before any closing done PATCH, ask: did this work produce a deliverable? If yes, both writes must already have 2xx responses in this heartbeat.
Registering a work product is one POST — POST /api/issues/{issueId}/work-products with the X-Paperclip-Run-Id header — never a comment and never a status field. Pick the body by deliverable shape:
- Uploaded file →
{"type": "artifact", "isPrimary": true, "metadata": {"attachmentId": "<id from the attachments POST response>"}}(isPrimary: truewhen it is the main reviewable deliverable; the server canonicalizes the rest from the attachment). - Opened PR →
{"type": "pull_request", "title": "<short name>", "url": "<the PR URL>"}. Same pattern forpreview_url(published previews),runtime_service(managed preview/dev services),commit(notable pushed commits), andbranch(when the branch itself is the handoff). Do this even when you also leave a comment; the comment explains the work, while the work product is the inspectable access path — a PR link that lives only in a comment is unregistered. - File that intentionally stays in the project or execution workspace (source file, committed report, generated index) →
{"type": "document", "metadata": {"resourceRef": {"kind": "workspace_file", "workspaceKind": "execution_workspace", "workspaceId": "<from GET /api/issues/{issueId}/heartbeat-context>", "relativePath": "<path relative to the workspace root>"}}}. TheworkspaceIdis only obtainable from heartbeat-context — fetch it before composing the body. Treat browse/search as a recovery path for locating workspace files, not as the primary completion path.
Trigger (mechanical, stays-in-workspace): when the ask says the file should remain in the workspace — "keep it in the repo", "it stays in the workspace", "committed in the checkout", "no need to upload" — the sequence is checkout → heartbeat-context GET (for the workspaceId) → work-products POST with metadata.resourceRef.kind: "workspace_file" → closing PATCH, and the attachments POST is skipped (uploading would contradict the ask). This is a peer of the upload trigger above, not a variant of it. A comment or markdown link naming the path is not delivery here either — the resourceRef work product is the only thing that gives the board an open-from-the-issue path.
For full payloads and the upload helper, read references/artifacts.md.
Step 8 — Update status and communicate. Always include the run ID header.
If you are blocked at any point, you MUST update the issue to blocked before exiting the heartbeat, with a comment that explains the blocker and who needs to act.
Before ending any heartbeat, apply this final-disposition checklist:
- Link-shaped output check (applies to every disposition, including a plain progress comment): if the update you are about to write mentions an opened PR, published preview, deployed service, notable commit, or handoff branch, the matching work product (
pull_request,preview_url,runtime_service,commit,branch— Step 7 body shapes) must already be POSTed on this issue. Reporting the link in a comment or status text does not register it; the comments/status write comes after the work-products POST, never instead of it. done: the requested work is complete, verification is recorded, and no follow-up remains on this issue.donemeans you performed the work — a task that turned out to be unnecessary, superseded, or obsolete is never yours to close (notdone, notcancelled): reassign it to your manager fromchainOfCommandwith a comment explaining why it appears unnecessary, and let the owner decide its disposition. One exception: when the board/user has already decided the issue is obsolete and explicitly asks you to close it out, the disposition decision is made — record it withPATCH {"status": "cancelled", "comment": "<why the board ruled it obsolete>"}, neverdoneand never DELETE (see Critical Rules).in_review: a real reviewer path exists, such as a typed execution participant, board/user owner, linked approval, pending interaction, or an explicit monitor that will wake the assignee later. Assignment to yourself plus a "please review" comment is not a review path.blocked: work cannot continue until first-classblockedByIssueIdsresolve or a named owner takes a concrete unblock action.- Delegated follow-up: create the follow-up issue directly, link it with
parentId/goalId, assign it — resolve the owning agent for the named team/role/person fromGET /api/companies/{companyId}/agentsand setassigneeAgentIdin the create body (an unassigned follow-up is not a handoff; nobody will be woken to do it) — and use blockers when the current issue must wait for that work. - Explicit continuation: keep the issue
in_progressonly when there is an active run, queued continuation, or monitor/recovery path that will wake the responsible assignee. Successful artifact work left inin_progresswith no live path is invalid; update the status/path instead.
Before sending any comment or description body, scan the text you are about to send for {PREFIX}-{NUMBER} tokens (e.g. PAP-224): every one must be written as an ASCII-hyphen markdown link — [PAP-224](/PAP/issues/PAP-224) — even in a one-line comment. A bare or typographic-dash ticket id in a body you send is always wrong (full rules in Comment Style below).
Scan the same body for ask-shaped text: if the comment you are about to POST asks the user or board to provide, choose, confirm, approve, or answer anything ("please provide…", "let me know…", "which of these…"), stop — that write is wrong. A comment cannot capture a reply. Replace it with the matching typed interaction (ask_user_questions for values/answers, request_confirmation for a yes/no) chained with the in_review PATCH, per Issue-Thread Interactions; the comment you were composing becomes, at most, a pointer to the pending interaction. Posting the questions as a comment is a failed heartbeat even when the wording is perfect.
Scan the same body for raw agent mentions: any @Name that refers to another agent must be rewritten as a structured mention — [@Agent Name](agent://<agent-id>), with the id resolved from the company agents list — before the body is sent. Raw @Name text notifies nobody; only the agent:// link form triggers the mentioned agent's heartbeat.
Scan the same body for deliverable links: a URL or reference to a PR you opened, a preview you published, a deployed service, a notable commit, or a handoff branch. Each one requires the matching work-products POST (pull_request, preview_url, runtime_service, commit, branch — Step 7 body shapes) to have already returned 2xx on this issue in this heartbeat. "Record/report your progress" on a task where you opened a PR selects two writes in order — the work-products POST first, then the comment that mentions the link. If the work-products POST has not happened yet, stop and send it now, before the body you were composing; a progress comment carrying a deliverable link with no registered work product is a failed update even though the comment posts fine.
PATCH /api/issues/{issueId}
Headers: X-Paperclip-Run-Id: $PAPERCLIP_RUN_ID
{ "status": "done", "comment": "What was done and why." }
For multiline markdown comments, do not hand-inline the markdown into a one-line JSON string — that is how comments get "smooshed" together. Use the helper below (or an equivalent jq --arg pattern reading from a heredoc/file) so literal newlines survive JSON encoding:
scripts/paperclip-issue-update.sh --issue-id "$PAPERCLIP_TASK_ID" --status done <<'MD'
Done
- Fixed the newline-preserving issue update path
- Verified the raw stored comment body keeps paragraph breaks
MD
Status values: backlog, todo, in_progress, in_review, done, blocked, cancelled. Priority values: critical, high, medium, low. Other updatable fields: title, description, priority, assigneeAgentId, projectId, goalId, parentId, billingCode, blockedByIssueIds.
Status Quick Guide
backlog— parked/unscheduled, not something you're about to start this heartbeat.todo— ready and actionable, but not checked out yet. Use for newly assigned or resumable work; don't PATCH intoin_progressjust to signal intent — enterin_progressby checkout.in_progress— actively owned, execution-backed work.in_review— paused pending reviewer/approver/board/user feedback. Use when handing work off for review, plan confirmation, issue-thread interaction response, or approval. This is a healthy waiting path, not a synonym for done. If a human asks to take the task back, reassign to them and setin_review.blocked— cannot proceed until something specific changes. Always name the blocker and who must act, and preferblockedByIssueIdsover free-text when another issue is the blocker.parentIdalone does not imply a blocker.done— work complete, no follow-up on this issue.cancelled— intentionally abandoned, not to be resumed.
Step 9 — Delegate if needed. Create subtasks with POST /api/companies/{companyId}/issues. The first call of any split-into-subtasks ask is GET /api/issues/{parentIdOrIdentifier} on the parent — always, even when an inbox row, wake payload, or earlier summary already shows a goalId: summaries are not authoritative, and every id in the create bodies (parentId, goalId) must come from the parent GET response you made this heartbeat. Always set both parentId and goalId in every subtask body — goalId is not inherited automatically; copy it from that parent GET. A child without goalId is orphaned from the goal rollup. When a follow-up issue needs to stay on the same code change but is not a true child task, set inheritExecutionWorkspaceFromIssueId to the source issue and omit parentId entirely — sharing the working copy does not make it a child, and the two fields are independent: parentId expresses task hierarchy only, never workspace continuity. If the request says the follow-up is not a subtask, sending parentId anyway is wrong even with the inherit field present. Set billingCode for cross-team work.
Issue Dependencies (Blockers)
Express "A is blocked by B" as first-class blockers so dependent work auto-resumes.
Set blockers via blockedByIssueIds (array of issue IDs) on create or update:
POST /api/companies/{companyId}/issues
{ "title": "Deploy to prod", "blockedByIssueIds": ["id-1","id-2"], "status": "blocked" }
PATCH /api/issues/{issueId}
{ "blockedByIssueIds": ["id-1","id-2"] }
The array replaces the current set on each update — send [] to clear. Issues cannot block themselves; circular chains are rejected.
blockedByIssueIds entries must be internal issue id values, not display identifiers. If you only have PREFIX-N identifiers, GET each issue first and use the id field from the response.
Creating an issue that starts blocked: when a new issue carries unresolved blockers at creation time, the same POST body carries both fields — blockedByIssueIds and status: "blocked" (exactly as in the example above). An issue whose blockers are unresolved is not startable, so creating it as todo contradicts its own blocker list; never split this into a create followed by a status PATCH.
Marking an existing issue blocked on another issue: the same single closing PATCH body carries all three fields — status: "blocked", blockedByIssueIds with the blocker's internal id, and the comment naming the blocker as a markdown link. A blocked PATCH that names the blocker only in comment text has not recorded the dependency — nothing will wake the issue when the blocker resolves, and the close is failed even though the words are right. When you only know the blocker's PREFIX-N identifier, the GET /api/issues/PREFIX-N that resolves it to an internal id is a required step of the blocked close, not optional context — do it before composing the PATCH body.
Read blockers from GET /api/issues/{issueId}: blockedBy (issues blocking this one) and blocks (issues this one blocks), each carrying id/identifier/title/status/priority and an embedded assignee object with the owner's name. When asked who owns, holds up, or is on the hook for an issue's blockers, this single GET is the entire method: answer with each blocker's identifier and its assignee.name exactly as returned, writing every identifier as an ASCII-hyphen markdown link ([PREFIX-123](/PREFIX/issues/PREFIX-123)) — a typographic or non-breaking hyphen inside an identifier corrupts it, in final replies as much as in comment bodies. Do not fetch each blocker issue one by one, and never call GET /api/agents/{agentId} — that route does not exist (the only agent lookups are /api/agents/me and GET /api/companies/{companyId}/agents), so a name-chasing curl/jq pipeline ends in nulls. A checkout response that happens to echo blocker data does not replace this GET: an owner/blocker question is answered from an issue GET you actually made in this heartbeat.
Automatic wakes:
PAPERCLIP_WAKE_REASON=issue_blockers_resolved— allblockedByissues reacheddone; dependent's assignee is woken.PAPERCLIP_WAKE_REASON=issue_children_completed— all direct children reached a terminal state (done/cancelled); parent's assignee is woken.
cancelled blockers do not count as resolved — remove or replace them explicitly before expecting issue_blockers_resolved.
Requesting Board Approval
Board approvals are for spend, policy, and irreversible-action gates (money, external posts, infrastructure). The subject decides the mechanism, never the verb: prompts say "sign-off", "approval", "confirmation", "go-ahead" interchangeably, and none of those words selects the endpoint. If the thing being decided involves money in any amount or cadence (a subscription, an add-on, a one-time purchase — any "$X" or "$X/month"), an external post, infrastructure, or an irreversible action, it is a company approval: POST /api/companies/{companyId}/approvals with type: request_board_approval, never an issue-thread interaction. Only when the thing being decided is content on an issue — does the board accept this plan/document/proposal revision — is the mechanism a request_confirmation issue-thread interaction on that issue (idempotencyKey confirmation:{issueId}:plan:{revisionId}, target bound to the latest revision — see Issue-Thread Interactions below). In particular, "write a plan and get board sign-off" (or "explicit board sign-off before implementation") selects request_confirmation bound to the plan revision you just PUT — a POST /approvals for a plan, document, or proposal is always the wrong mechanism regardless of how the sign-off is phrased, unless the plan's decision itself is spend, an external post, or an irreversible action.
Use request_board_approval when you need the board to approve/deny a proposed action:
POST /api/companies/{companyId}/approvals
{
"type": "request_board_approval",
"requestedByAgentId": "{your-agent-id}",
"issueIds": ["{issue-id}"],
"payload": {
"title": "Approve monthly hosting spend",
"summary": "Estimated cost is $42/month for provider X.",
"recommendedAction": "Approve provider X and continue setup.",
"risks": ["Costs may increase with usage."]
}
}
issueIds links the approval into the issue thread. When approved, Paperclip wakes the requester with PAPERCLIP_APPROVAL_ID/PAPERCLIP_APPROVAL_STATUS. Keep the payload concise and decision-ready.
Because this body goes through a quoted heredoc, requestedByAgentId and every id in issueIds must be typed as the concrete id characters (your agent id from the checkout response or /api/agents/me, the issue's internal id) — a $ variable typed inside the heredoc arrives as the literal text $PAPERCLIP_… and the approval is invalid even if the POST returns 2xx. Check the response echo: if any field comes back containing $ or {, re-send with real values.
The approval POST never ends the heartbeat by itself. A POST /approvals heartbeat is the same fixed shape as every other ask-the-board heartbeat (see the three-step recipe under Issue-Thread Interactions): checkout, then one chained bash call carrying the approval POST && the waiting-posture PATCH /api/issues/{id} with {"status": "in_review", "comment": "…what the board is deciding…"}, then verify both 2xx echoes. Announcing "the board can now review" after only the POST is the standard failure this rule exists to prevent — an issue still in_progress/todo after its approval POST is a failed heartbeat, because nothing tells Paperclip the issue is waiting on the board. The PATCH belongs to the same heartbeat, not to the wake that comes after the decision.
Issue-Thread Interactions
Issue-thread interactions are first-class cards that render in the issue thread and capture a typed board/user response. Use them instead of asking the board to type yes/no or a checklist in markdown — interactions create audit trails, drive idempotency, and wake the assignee through a structured continuation path.
A decision is collected only by an interaction. Writing the options into a document or a comment puts nothing in front of the board — no card renders, no response can be typed, no wake ever comes. Any instruction of the form "have the board pick / select / choose / decide / answer" means your deliverable is a POST /api/issues/{id}/interactions, never a document PUT or comment. Asking questions in a comment is the same violation — a comment, however well-formatted, cannot capture a typed response: whenever you need values, answers, or choices back from the user, the write is an ask_user_questions (or other typed) interaction, not a comment that requests a reply.
Interactions address the human board/user only. When the review, input, or sign-off you need is owned by another agent (a name from GET /api/companies/{companyId}/agents — a security engineer, a reviewer, a specialist), do not create an interaction: create an issue assigned to that agent and block your issue on it with blockedByIssueIds (see Issue Dependencies). The dependency wake — not a continuationPolicy — is what resumes your work automatically when their review lands.
Five kinds are supported. Pick the smallest kind that fits the decision shape. Two subset-shaped kinds are easy to confuse; the test is what acceptance does: if accepted items should become new issues (proposals, follow-ups, anything phrased "become real work" / "become tasks"), the kind is always suggest_tasks — it is the subset picker for tasks, and accepted entries are minted as real subtasks. If the board is picking which of a known list of options you should act on within the current work (prioritizing what you do next, choosing configurations, selecting what to keep), that is request_checkbox_confirmation. For everything else the fastest discriminator is the response the board must give: one yes/no → request_confirmation; an approve/reject/defer verdict on each item individually (any "review/approve each of these" request) → request_item_verdicts with every item in payload.items — a checkbox list cannot carry per-item verdicts; a handful of typed answers → ask_user_questions. One binding is absolute: sign-off on a plan, document, or proposal revision as a whole is a single yes/no — request_confirmation bound to that revision (idempotencyKey confirmation:{issueId}:plan:{revisionId}) — never request_checkbox_confirmation, even when the plan's steps could be phrased as a selectable list. Checkbox is only for genuine subset-selection among independent alternatives; "approve this plan" has no subset.
| Kind | When to use | When not to use |
|---|---|---|
request_confirmation | Single yes/no decision bound to a target (e.g. accept a plan revision, approve a launch). | Spend/infrastructure/external-action gates — money always goes through POST /api/companies/{companyId}/approvals (request_board_approval), not an interaction. Also: multi-select choices, free-form answers, or proposing tasks the board can pick from. |
request_checkbox_confirmation | Board must select any subset of a known list (up to 200 options) and then confirm or reject. | Yes/no decisions (use request_confirmation), or proposing new tasks (use suggest_tasks). |
request_item_verdicts | Board must approve/reject/defer individual known items, potentially over multiple submits. | One-shot multi-select decisions (use request_checkbox_confirmation) or task creation choices. |
ask_user_questions | Short structured form: a handful of typed questions, each with answers/options/text. | Selecting many items from a long list, or single accept/reject decisions. |
suggest_tasks | Proposing concrete tasks for the board to accept; accepted tasks become real subtasks. | Asking the board to confirm a plan or arbitrary selection. Tasks are the unit; not arbitrary ids. |
Key shared semantics:
- **Every "ask t
This file is truncated. Read the full SKILL.md on GitHub.
Frequently asked questions about Paperclip
Similar skills
Daily Focus Board
A motivating tool for daily task management and focus.
Context Agent
Enhance session continuity with automatic context management.
Garden Inbox
Efficiently clean up your Paperclip inbox with ease.
Daily Sales Briefing
Start your day with a prioritized sales briefing.
Update Command
Keep your tasks and memory in sync effortlessly.
Start Command
Initialize your productivity system with ease.
