From 224d150d86ff9719169b17e5d57bb6c87284588f Mon Sep 17 00:00:00 2001 From: Forgotten Date: Thu, 19 Feb 2026 09:09:50 -0600 Subject: [PATCH] Inject run context env vars into local adapters and update SKILL.md Inject PAPERCLIP_RUN_ID, PAPERCLIP_TASK_ID, and PAPERCLIP_WAKE_REASON into local adapter environments so agents know their run context and wake trigger. Update SKILL.md to document new env vars and require X-Paperclip-Run-Id header on all mutating API requests for audit trail linkage. Co-Authored-By: Claude Opus 4.6 --- .../adapters/claude-local/src/server/execute.ts | 15 +++++++++++++++ .../adapters/codex-local/src/server/execute.ts | 15 +++++++++++++++ skills/paperclip/SKILL.md | 11 ++++++++--- 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/packages/adapters/claude-local/src/server/execute.ts b/packages/adapters/claude-local/src/server/execute.ts index 9c0c75a7..ff71fdf8 100644 --- a/packages/adapters/claude-local/src/server/execute.ts +++ b/packages/adapters/claude-local/src/server/execute.ts @@ -66,6 +66,21 @@ export async function execute(ctx: AdapterExecutionContext): Promise 0; const env: Record = { ...buildPaperclipEnv(agent) }; + env.PAPERCLIP_RUN_ID = runId; + const wakeTaskId = + (typeof context.taskId === "string" && context.taskId.trim().length > 0 && context.taskId.trim()) || + (typeof context.issueId === "string" && context.issueId.trim().length > 0 && context.issueId.trim()) || + null; + const wakeReason = + typeof context.wakeReason === "string" && context.wakeReason.trim().length > 0 + ? context.wakeReason.trim() + : null; + if (wakeTaskId) { + env.PAPERCLIP_TASK_ID = wakeTaskId; + } + if (wakeReason) { + env.PAPERCLIP_WAKE_REASON = wakeReason; + } for (const [k, v] of Object.entries(envConfig)) { if (typeof v === "string") env[k] = v; } diff --git a/packages/adapters/codex-local/src/server/execute.ts b/packages/adapters/codex-local/src/server/execute.ts index a46ab217..55cc12c4 100644 --- a/packages/adapters/codex-local/src/server/execute.ts +++ b/packages/adapters/codex-local/src/server/execute.ts @@ -34,6 +34,21 @@ export async function execute(ctx: AdapterExecutionContext): Promise 0; const env: Record = { ...buildPaperclipEnv(agent) }; + env.PAPERCLIP_RUN_ID = runId; + const wakeTaskId = + (typeof context.taskId === "string" && context.taskId.trim().length > 0 && context.taskId.trim()) || + (typeof context.issueId === "string" && context.issueId.trim().length > 0 && context.issueId.trim()) || + null; + const wakeReason = + typeof context.wakeReason === "string" && context.wakeReason.trim().length > 0 + ? context.wakeReason.trim() + : null; + if (wakeTaskId) { + env.PAPERCLIP_TASK_ID = wakeTaskId; + } + if (wakeReason) { + env.PAPERCLIP_WAKE_REASON = wakeReason; + } for (const [k, v] of Object.entries(envConfig)) { if (typeof v === "string") env[k] = v; } diff --git a/skills/paperclip/SKILL.md b/skills/paperclip/SKILL.md index e4a48c57..f6b45f9c 100644 --- a/skills/paperclip/SKILL.md +++ b/skills/paperclip/SKILL.md @@ -14,7 +14,9 @@ You run in **heartbeats** — short execution windows triggered by Paperclip. Ea ## Authentication -Env vars auto-injected: `PAPERCLIP_AGENT_ID`, `PAPERCLIP_COMPANY_ID`, `PAPERCLIP_API_URL`. For local adapters, `PAPERCLIP_API_KEY` is auto-injected as a short-lived run JWT. 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. +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) and `PAPERCLIP_WAKE_REASON` (why this run was triggered). For local adapters, `PAPERCLIP_API_KEY` is auto-injected as a short-lived run JWT. 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. + +**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 @@ -25,10 +27,12 @@ Follow these steps every time you wake up: **Step 2 — Get assignments.** `GET /api/companies/{companyId}/issues?assigneeAgentId={your-agent-id}&status=todo,in_progress,blocked`. Results sorted by priority. This is your inbox. **Step 3 — Pick work.** Work on `in_progress` first, then `todo`. Skip `blocked` unless you can unblock it. **If nothing is assigned, exit the heartbeat. Do not look for unassigned work. Do not self-assign.** +If `PAPERCLIP_TASK_ID` is set and that task is assigned to you, prioritize it first for this heartbeat. -**Step 4 — Checkout.** You MUST checkout before doing any work: +**Step 4 — Checkout.** You MUST checkout before doing any work. Include the run ID header: ``` POST /api/issues/{issueId}/checkout +Headers: Authorization: Bearer $PAPERCLIP_API_KEY, X-Paperclip-Run-Id: $PAPERCLIP_RUN_ID { "agentId": "{your-agent-id}", "expectedStatuses": ["todo", "backlog", "blocked"] } ``` If already checked out by you, returns normally. If owned by another agent: `409 Conflict` — stop, pick a different task. **Never retry a 409.** @@ -37,9 +41,10 @@ If already checked out by you, returns normally. If owned by another agent: `409 **Step 6 — Do the work.** Use your tools and capabilities. -**Step 7 — Update status and communicate.** +**Step 7 — Update status and communicate.** Always include the run ID header: ``` PATCH /api/issues/{issueId} +Headers: X-Paperclip-Run-Id: $PAPERCLIP_RUN_ID { "status": "done", "comment": "What was done and why." } ``` 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`.