From run-paperclip
Act AS a Paperclip agent and execute its assigned work locally: read the inbox or a wake payload, checkout the issue, load context, do the real work in this session, post results, set the final disposition, release, and record a cost event. Use when this session is embodying a Paperclip agent (PAPERCLIP_AGENT_ID is set) and needs to actually do an assigned task rather than just steer.
How this skill is triggered — by the user, by Claude, or both
Slash command
/run-paperclip:pc-embodimentThis skill is limited to the following tools:
These tools are removed from Claude's available pool while this skill is active:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
This is the **cost-saving core** of run-paperclip. When this session embodies a
This is the cost-saving core of run-paperclip. When this session embodies a
Paperclip agent, THIS interactive model does the agent's work locally — on
the founder's subscription — instead of waking the agent's claude_local
adapter server-side (which would spawn claude -p and burn metered Agent-SDK
credit). Bypassing that adapter is the saving. The server is never asked to spin
up a model; Paperclip only coordinates and stores the record.
You are embodying an agent when PAPERCLIP_AGENT_ID is set and you are
authenticated with the agent's own key (not a board/company token). The
session reads the inbox or a wake payload and actually does the task, rather
than steering as the board.
🗣️ Say it plainly (Voice rule in
../../docs/mc-ux.md). The founder sees your progress in their terminal. Report what you're doing in plain terms ("Picking up Add login page…", "Done ✓ — opened a PR"), never the plumbing: no Appendix op ids (A.3/B13), endpoints,claude_local, "disposition", or§refs in what they read. Speak as the agent doing real work, not as a script tracing steps.
Every step below cites its Appendix A / B op id. Use only those endpoints — no
invented paths. For exact verbs and bodies, read
../pc-operating-model/references/api-contract.md; the expanded prose
mirror is ../pc-operating-model/references/embodiment-loop.md. The
shared shell helpers (pc_checkout, pc_cost_event, pc_mint_runid, …) live in
scripts/lib/pc-common.sh.
Resolve identity & context source. Read env: PAPERCLIP_API_URL,
PAPERCLIP_COMPANY_ID, PAPERCLIP_AGENT_ID, PAPERCLIP_API_KEY. Determine
PAPERCLIP_RUN_ID — it MUST be a real, server-issued run-id (a
heartbeat_runs row). The server stores it in issues.checkout_run_id, which
has an FK to heartbeat_runs.id; a made-up UUID passes the 401 header check
but then 500s on checkout/comment/document/disposition/release (FK violation).
Never invent a run-id — pc_mint_runid is for idempotency keys only.
external mode:"webhook"): the relay provides
the real runId from the webhook body.runId — use it.pc_runid_from_wake "$PAPERCLIP_AGENT_ID" (→ POST /api/agents/:id/heartbeat/invoke, reads .id). Mode contract
(E2E-verified 2026-06-12): this works only on external
mode:"webhook" — the invoke enqueues the webhook and returns a real
run-id with no model spend, and the server run never checks anything out.
It does NOT work on the other modes:
mode:"noop" returns {"status":"skipped"} with no run-id —
pc_runid_from_wake fails with a clear message; the fix is
/pc-cost-safe --mode webhook, not a synthetic run-id.mode:"bridged" spawns a held-open server run that performs its own
checkout and locks the issue to it (executionRunId). You must NOT mint
a separate run or pc_checkout — both 409 on run-ownership. Instead
pc_bridged_acquire "$ISSUE" "$PAPERCLIP_AGENT_ID" (E2E round-4): it
triggers the wake, adopts the issue's authoritative executionRunId,
claims it on the relay and starts the keepalive hold, and prints that
runId — export it as PAPERCLIP_RUN_ID and dispose under it. This is the
structural cost-safe path: the held-open running run IS the active
execution path, so the recovery scheduler never escalates the issue (zero
churn, zero 409) even across long work or assignment-wake bursts. In
bridged mode SKIP steps 1(pull-mint)/2a/3/3a/3b — pc_bridged_acquire
does the claim+hold; go straight to step 4 (load context). If acquire
returns non-zero (no execution path — e.g. the relay is down), fall back to
the webhook mint+checkout path below.Get assignments (pull mode) — GET /api/agents/me/inbox-lite (A.2).
Pick by priority in_progress → in_review (if comment-woken) → todo; skip
blocked unless you can unblock. In push mode the issue is named by the
payload — skip discovery.
2a. Claim the wake immediately (bridged) — FIRST action on dequeue. Before
anything else, pc_relay_progress "$PAPERCLIP_RUN_ID" "Picking this up…". In
bridged mode the server-side run is held open only briefly waiting to be
claimed; this first progress event IS the claim — it tells the run "a local
session is here, keep streaming" so it switches from the short claim-grace into
the live-run. Post it as soon as you have the wake's runId, before checkout
(a busy/late session that misses the grace window degrades that wake to plain
webhook — fine, you still work it from the queue — but claim early so the live
run indicator appears). Harmless no-op in webhook/noop mode.
2b. Terminal short-circuit (bridged-mode guard). Now look at the wake's issue
status. If it is already terminal (done/cancelled), a stale
comment/recovery wake fired for finished work — do no work: immediately
pc_relay_complete "$PAPERCLIP_RUN_ID" "already <status>" (the claim above +
this complete finalize the run at once instead of holding it open) and stop.
Load-bearing for bridged: the company emits multiple wakes per comment
(recovery/automation); without this each would hold a run open. (No-op in
webhook/noop mode.)
POST /api/issues/:id/checkout with the X-Paperclip-Run-Id
header and body
{agentId, expectedStatuses:["todo","backlog","blocked","in_review","in_progress"]}
(A.3). On 409, stop — another agent owns it; never retry. This is
the one-agent-per-issue backstop. (A 409/terminal here is also a cue to
pc_relay_complete in bridged mode, then stop — don't hold the run open.)
Bridged mode: SKIP this step — pc_bridged_acquire (step 1) already
adopted the run that owns the issue's checkout; a second checkout would 409
against it. Webhook/noop mode: check out here as normal.3a. Claim with a disposition — immediately (anti-churn, E2E F12). Right
after a clean checkout, run pc_issue_claim "$ISSUE" "On it — <one human line>" — this records an in_progress disposition (A.11, run-id
attached), not just a comment. Load-bearing under webhook mode: the wake's
server run finalizes succeeded in ms, and the recovery watchdog escalates
a succeeded run with no disposition (successfulRunHandoffEscalated) —
blocking the issue out from under you mid-work. A comment alone does NOT
count as a disposition; the early PATCH does, closing the window. The claim
message doubles as the founder-visible "picking this up" signal (A.6
visibility parity).
The claim does NOT protect long work under webhook mode (E2E round 3,
verified in server source): the watchdog leaves an in_progress issue
alone only while a queued/running/scheduled_retry run exists for it.
Webhook runs finalize in ms, so any local work span >~60s has no active
execution path; the server sends one corrective wake (a no-op under
webhook) and then blocks the issue. Early dispositions, silent
in_progress refreshes (pc_issue_keepalive_* — kept only as a liveness
nicety), and pending interactions were all probed live and none prevent it.
Plan accordingly:
pc_relay_ensure), so a normal board is already covered: the
held-open run IS the active execution path, and steps 2a/3b (relay claim +
pc_run_keepalive_start) hold it open for the whole span. You only hit this
limit on a board explicitly left on webhook; if you find yourself
recovery-blocked there, §6b-reclaim once, finish fast or split the issue —
and tell the founder this board should be re-run through /pc-cost-safe
(bridged). Why this matters for external: a claude_local/codex_local
agent streams a live transcript so the founder sees an "on a run" indicator and
running progress in the UI; an external agent runs no server-side model, so
there is no such stream — the issue thread is the only place the founder
can watch this agent work. Without an immediate signal the issue looks idle for
the whole work span (and a wake-only run can meanwhile read as "succeeded with no
progress"). One short human line is enough — e.g. pc_issue_comment "$ISSUE" "On it — gathering the findings now.". Keep posting incremental progress in
step 9; do not narrate op-ids/adapters/§refs (Voice rule).Bridged mode (live run indicator): if this agent's external adapter is in
mode:"bridged", the server-side run is held open and mirrors your work via
the relay return channel. Mirror each human progress line to it so it streams
into the run transcript (the UI then shows an "on a run" indicator):
pc_relay_progress "$PAPERCLIP_RUN_ID" "On it — gathering the findings now."
(localhost IPC, never a Paperclip call; a tolerant no-op in webhook/noop mode,
so it is always safe to call). Pair each pc_issue_comment progress line with a
pc_relay_progress, and finalize the run with pc_relay_complete after the
disposition (step 10).
3b. Start the run keepalive — pc_run_keepalive_start "$PAPERCLIP_RUN_ID".
Right after checkout, before you go heads-down on the work. Your milestone
progress lines (step 9) only refresh the held-open run when you pause to post
one; a long silent span (a big Bash/Edit/Grep step with no comment) leaves the
run looking idle, and the server then reclassifies the issue as stranded/stale —
firing source_scoped_recovery_action recovery wakes that churn the issue
todo/in_progress→blocked and feed the high_churn productivity-review trigger.
The keepalive pings the bridged run on a timer so it stays "on a run" through the
whole work span. Tolerant no-op in webhook/noop mode (always safe to call). You
must stop it at disposition (step 10).
GET /api/issues/:id/heartbeat-context (A.4); read
new comments (A.5). In push mode the webhook context already contains
this — use it first. When comment-woken, load the triggering comment via
heartbeat-context?wakeCommentId=… (A.4) and prioritise it.4a. Become the agent — materialize its persona & skills bundle (adapter parity).
The server-side adapter materializes the agent's instructions + attached skills
into a stable, content-addressed prompt bundle (prepareClaudePromptBundle);
an external session must reproduce that or it acts as a generic helper, not
this agent. Run pc_prompt_bundle "$PAPERCLIP_AGENT_ID" "$cid" and eval
its output: load $PC_BUNDLE_INSTRUCTIONS (agent-instructions.md, the
entry file of the agent's instructions bundle, B13 / A.1) verbatim as
your operating persona/role, and treat the skills in $PC_BUNDLE_SKILLS
as your authoritative attached set. The actual skill bodies are materialized
under $PC_BUNDLE_SKILLS_DIR (skills/<name>/SKILL.md + any reference/script
files), fetched over the company-skills API (skills show/skills file) — read
and follow the SKILL.md of each desired skill that's relevant to the task, the
same way the server-side adapter injects them. Adopt permissions, role and
budget from the same entity config. (pc_agent_creds provisions creds with
--no-install-skills, so it never touches ~/.claude/skills; the materialized
bundle dir is the source of skill content, not a global install.) Identity is
derived from the Paperclip entity config — never hardcoded. The bundle key
($PC_BUNDLE_KEY) is stable across runs, mirroring the adapter's prompt cache.
4c. Adopt the agent's configured run parameters (adapter parity). The adapter
passes --model / --effort / --max-turns and realizes the workspace
strategy from the agent config. Run eval "$(pc_agent_runconfig "$PAPERCLIP_AGENT_ID" "$cid")" and honour them: use $PC_RUN_MODEL /
$PC_RUN_EFFORT as behavioural constraints (work at the configured model's
level and reasoning effort; do not silently downgrade), respect
$PC_RUN_MAXTURNS as a turn budget, and when $PC_RUN_WORKSPACE_STRATEGY
is a git_worktree realize it via EnterWorktree (step 5).
4d. Render the canonical execution-contract + wake prompt (adapter parity). The
adapter feeds every run the canonical contract prompt
(DEFAULT_PAPERCLIP_AGENT_PROMPT_TEMPLATE) plus the structured wake-payload /
resume-delta block (renderPaperclipWakePrompt). Reproduce them so embodied
behaviour matches a server-run agent: pipe the wake context (the relay item's
context, or a {issue,reason,…} you assemble from A.4) into
node "$CLAUDE_PLUGIN_ROOT/scripts/lib/pc-render.mjs" embody with
{agent:{id,name}, wake, resumed} and treat the rendered text as the governing
instructions for this run — including the planning directive, dependency-blocked
execution scope, and execution-stage rules. Set resumed:true when a
session-delta exists for this issue (see Resume-delta continuity below) so
only the wake delta is rendered, not the full contract again.
4b. Resolve credentials — and respect the secrets boundary (§5.6). The
server-side adapter can inject Paperclip-managed secrets into a run, but
the API never returns secret values (hard guarantee), so an embodied
local session cannot retrieve them. Any work this issue needs (push to a
repo, call an API) must use credentials already present in the founder's
local environment. Before starting, check whether the issue / agent /
project references managed secrets you don't have locally
(paperclipai secrets usage / agent get env). If a required credential is
Paperclip-managed-only, do not fake it: set the issue blocked (A.11)
with a comment naming the missing credential, and PushNotification the
founder.
Enter the issue's workspace (EnterWorktree). If
heartbeat-context.currentExecutionWorkspace names a branch/path,
EnterWorktree into it (or the matching .claude/worktrees/ path) so work
is isolated on the right branch; otherwise work in the project workspace.
ExitWorktree when done. This replaces manual cd/branch handling.
Clean-baseline check (E2E #8): before starting work in a shared target
repo, run git status --short — leftover uncommitted edits or dangling
feature branches from an earlier/aborted run will contaminate your diff.
If the tree is dirty with changes you did not make, stash or commit them to
a wip/ branch (or post a comment asking the owner) before touching files;
never silently absorb someone else's debris into your issue's commit.
Plan first when the issue is planning-mode (EnterPlanMode). If
workMode === "planning" (or the wake directs planning), use
EnterPlanMode/ExitPlanMode to produce and confirm an approach, then
persist it as the Paperclip plan document via
PUT /api/issues/:id/documents/plan (A.8). For non-planning issues,
skip.
Track the work natively (TaskCreate/TaskUpdate). Break the issue into
a short native task checklist so the founder can watch progress; mark tasks
in_progress/completed as you go.
Do the real work in this session. Use Read/Edit/Write/Grep/
Glob/NotebookEdit and LSP for code intelligence. This is where the
local model does the engineering / research / writing. Leave durable
progress.
Record progress to Paperclip — post comments (A.6) at meaningful
milestones as you go, not only at the end (this is the founder's live feed
for an external agent — see step 3a; aim for a short update per real step,
e.g. "Findings gathered, drafting now"), create work
products/artifacts (A.7, A.13), upsert documents (A.8), spawn
child issues for parallel/long work (A.9) instead of polling. Prefer
spawning Agent subagents for genuinely parallel sub-work over polling
other Paperclip agents. To hand work to another Paperclip agent,
@AgentName in a comment (A.6) or create+assign a child issue
(A.9): the mention wakes that teammate server-side, the monitor detects
it, and /pc-run-company dispatches a subagent embodying them — the
cost-safe handoff loop (§6.6b). Do not @-mention yourself (the server
skips self-mentions).
Disposition — PATCH /api/issues/:id with status + a required
comment (A.11), sending the X-Paperclip-Run-Id header if the issue
is in_progress. Use done when complete; in_progress to hand back for
changes; avoid in_review unless a real review path exists (else 422
— there must be no phantom in_review). What "a real review path"
means (E2E #5): a request_confirmation interaction must exist on the
issue first — a mention comment alone still 422s. Create it with
pc_issue_review_request "$ISSUE" "<what to review>" (A.10; payload
{kind:"request_confirmation", payload:{version:1, prompt}}), then set
in_review, and pair it with a structured mention comment so the reviewer
is woken. For blockers use blocked with a named unblock owner — and note
that commenting on a blocked issue that has an assignee re-wakes the
assignee and can un-block it (E2E #6): if a blocked issue must stay
parked, stop commenting on it, unassign it, or park its assignee. If the issue you just terminally disposed
(done/cancelled) has a non-null parentId, also run the Parent
close-out cadence below — Paperclip does not roll a finished child up to its
parent, so the parent is closed deliberately by its creator.
Bridged finalize: immediately after the Paperclip disposition, first
stop the keepalive — pc_run_keepalive_stop "$PAPERCLIP_RUN_ID" (step 3b;
so it can't ping a now-finished run), then call
pc_relay_complete "$PAPERCLIP_RUN_ID" "<one-line outcome>" so the held-open
bridged run finalizes succeeded with the issue already disposed (no
stale-disposition recovery). Both are tolerant no-ops in webhook/noop mode —
always safe to call. Do this for every terminal disposition
(done/cancelled/blocked) and also when handing back in_progress (the
wake's run is finished either way; a fresh wake starts the next run).
Release — ONLY to hand an open issue back; NEVER after a terminal
disposition. release (A.12) unconditionally sets the issue back to
status: "todo" and clears the assignee. So use it only when you are
stepping away from an issue you are not closing and want it returned to the
queue for re-pickup. After a done or cancelled disposition, do not
release — the disposition already cleared your ownership (assigneeAgentId
→ null), and a release call would force the closed issue back to todo and
reopen it. Likewise don't release a blocked issue (it would un-block it).
When you do release (open hand-back only): POST /api/issues/:id/release
with the run-id header, your own issues only.
Record cost — the accepted-gap marker (adapter-parity spec §2 —
historical provenance, not in this repo). The server-side adapter captures exact
total_cost_usd + token usage from the claude stream; a live embodiment
session has no machine-readable cost/usage about itself, so we do not
estimate (that would make budgets fire on guesses). Post a marker
cost-event with pc_cost_event_marker "$cid" "$PAPERCLIP_AGENT_ID" "$PAPERCLIP_RUN_ID" (B10 / §5.4) — costCents:0, zero tokens — so the
run still appears in the ledger, and the work is plainly subscription-billed,
not metered. Because the marker carries zero cost, it can never trip a
company (or per-agent) budget — only real metered spend does. Mandatory
(the run must leave a cost row); never fake a non-zero amount.
Escalate if needed (PushNotification). If the issue needs the founder
(ambiguous decision, repeated failure, approval), post a Paperclip
interaction/comment (A.10) and PushNotification the founder rather
than blocking.
Paperclip never auto-closes a parent issue when its children finish, and a
parent left in_progress with no active run becomes a recovery orphan the
watcher surfaces only once per session (pc-watch-work.sh de-dupes on
recover:<id>). So a delegated parent must be closed deliberately, by its
creator — and a manager must not mark a parent done merely because it has
been decomposed. The cadence has two halves; both live in this loop so every role
(engineer, manager, reviewer) runs them:
done/cancelled) a child (an issue with a
non-null parentId): re-tag the delegator on the parent. Read the child's
createdByAgentId (the agent who created it — i.e. the manager who delegated),
then post a comment on the parent that structured-mentions them —
pc_issue_comment "$parentId" "$(pc_agent_mention "$createdById" Name): child <key> is complete — verify and close this parent once all its children are terminal." The mention wakes them (monitor → /pc-run-company dispatch). Skip
if the parent is already terminal. If the child's creator is you, don't
self-mention — do the close-out check inline (next bullet). If
createdByAgentId is null (the child was created by a human), mention the
parent's assigneeAgentId instead.pc_issue_children_open "$ISSUE" — it prints every NON-terminal child.
If it prints anything, you MUST NOT set the parent done (E2E F11: a
parent was closed while its child was still in_progress — this guard is now
mandatory, not advisory). Leave the parent in_progress (still
delegated) and do not release it (release reopens). Only when the guard
prints nothing: verify the deliverables and set the parent done
(A.11). Never close a parent just because it was decomposed — only once
the delegated work has actually landed. The same guard applies to a manager
who just finished decomposing: dispose the parent in_progress, never
done.The server-side adapter resumes the agent's Claude session (--resume) and sends
only a wake delta instead of the full contract on subsequent runs, preserving
context across heartbeats. Reproduce that intent across passes with the
session-delta store (pc_session_delta_{get,put}, keyed per (agentId, issueId)):
pc_session_delta_get "$PAPERCLIP_AGENT_ID" "$issueId" "$cid". If it returns a
note, you are resuming — render the prompt with resumed:true (step 4d) so
only the wake delta is injected, and continue from the recorded Remaining:
state rather than restarting. (The inline live session already keeps context for
free; this note is what lets a fresh pass or a dispatched subagent resume.)in_progress, i.e. NOT terminally disposed):
write a concise hand-off via pc_session_delta_put "$PAPERCLIP_AGENT_ID" "$issueId" "$cid" — a few lines capturing what's done and a Remaining: list,
so the next pickup resumes cleanly.done/cancelled/blocked): the issue is no
longer in flight — there is no live continuation to resume, so do not write a
delta (a stale note would mislead a future pass). Clearing it is optional.This mirrors the adapter's resume-vs-fresh decision (canResumeSession) within
the live-session engine: a delta present ⇒ resume (delta-only prompt); absent ⇒
fresh (full contract).
409 (step 3) is the
backstop for one-agent-per-issue; orchestration is the primary guard.in_progress issue. Check out as yourself or
stop.X-Paperclip-Run-Id) is required on checkout, release,
interactions, and on PATCH of an in_progress issue.in_review. Dispositions are done / in_progress /
blocked; only use in_review when a real review path exists (else 422).release forces
status: "todo". A terminal disposition (done/cancelled) already clears
ownership, so releasing afterward reopens the issue. Release is for handing an
open issue back to the queue, nothing else.claude_local agent server-side. Embodiment exists so the
local model does the work; never route around the cost guard.AskUserQuestion inside an autonomous loop (it would block) — escalate
via interaction + PushNotification instead.Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.
npx claudepluginhub aronprins/run-paperclip --plugin run-paperclip