From 62d8b3947425a0985ac998a15b66ae3c38ed8bf4 Mon Sep 17 00:00:00 2001 From: Aron Prins Date: Wed, 1 Apr 2026 13:49:11 +0200 Subject: [PATCH] feat(skills): add paperclip-routines skill MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a new skill that documents how to create and manage Paperclip routines — recurring tasks that fire on a schedule, webhook, or API call and dispatch an execution issue to the assigned agent. --- skills/paperclip-routines/SKILL.md | 210 +++++++++++++++++++++++++++++ 1 file changed, 210 insertions(+) create mode 100644 skills/paperclip-routines/SKILL.md diff --git a/skills/paperclip-routines/SKILL.md b/skills/paperclip-routines/SKILL.md new file mode 100644 index 00000000..fcad88fa --- /dev/null +++ b/skills/paperclip-routines/SKILL.md @@ -0,0 +1,210 @@ +--- +name: paperclip-routines +description: > + Create, manage, and trigger Paperclip routines — recurring tasks that fire on + a schedule (cron), webhook, or API call and create an execution issue for the + assigned agent. Use when you need to set up recurring work, add or modify + triggers, inspect run history, or reason about concurrency and catch-up + behaviour. +--- + +# Paperclip Routines + +Routines are recurring tasks. Each time a routine fires it creates an execution issue assigned to the routine's agent — the agent picks it up in the normal heartbeat flow. + +A routine has: +- One assigned agent and one project +- One or more triggers (schedule, webhook, or API) +- A concurrency policy (what to do when a previous run is still active) +- A catch-up policy (what to do with missed scheduled runs) + +**Authorization:** Agents can read all routines in their company but can only create/manage routines assigned to themselves. Board operators have full access including reassignment. + +--- + +## Lifecycle + +``` +active <-> paused +active -> archived (terminal — cannot be reactivated) +``` + +Paused routines do not fire. Archived routines do not fire and cannot be unarchived. + +--- + +## Creating a Routine + +``` +POST /api/companies/{companyId}/routines +{ + "title": "Weekly CEO briefing", + "description": "Compile status report and post to Slack", + "assigneeAgentId": "{agentId}", + "projectId": "{projectId}", + "goalId": "{goalId}", // optional + "parentIssueId": "{issueId}", // optional — parent for run issues + "priority": "medium", + "status": "active", + "concurrencyPolicy": "coalesce_if_active", + "catchUpPolicy": "skip_missed" +} +``` + +| Field | Required | Notes | +|-------|----------|-------| +| `title` | yes | Max 200 chars | +| `assigneeAgentId` | yes | Agents: must be themselves | +| `projectId` | yes | | +| `goalId` | no | Inherited by run issues | +| `parentIssueId` | no | Run issues become children of this issue | +| `priority` | no | `critical` `high` `medium`(default) `low` | +| `status` | no | `active`(default) `paused` `archived` | +| `concurrencyPolicy` | no | See below | +| `catchUpPolicy` | no | See below | + +--- + +## Concurrency Policies + +Controls what happens when a trigger fires while the previous run issue is still open/active. + +| Policy | Behaviour | +|--------|-----------| +| `coalesce_if_active` **(default)** | New run is marked `coalesced` and linked to the existing active run — no new issue created | +| `skip_if_active` | New run is marked `skipped` and linked to the existing active run — no new issue created | +| `always_enqueue` | Always create a new issue regardless of active runs | + +--- + +## Catch-Up Policies + +Controls what happens with scheduled runs that were missed (e.g. server downtime). + +| Policy | Behaviour | +|--------|-----------| +| `skip_missed` **(default)** | Missed runs are dropped | +| `enqueue_missed_with_cap` | Missed runs are enqueued, capped at 25 | + +--- + +## Adding Triggers + +A routine can have multiple triggers of different kinds. + +``` +POST /api/routines/{routineId}/triggers +``` + +### Schedule (cron) + +```json +{ + "kind": "schedule", + "cronExpression": "0 9 * * 1", + "timezone": "Europe/Amsterdam" +} +``` + +- `cronExpression`: standard 5-field cron syntax +- `timezone`: IANA timezone string (e.g. `UTC`, `America/New_York`) +- The server computes `nextRunAt` automatically + +### Webhook + +```json +{ + "kind": "webhook", + "signingMode": "hmac_sha256", + "replayWindowSec": 300 +} +``` + +- `signingMode`: `bearer` (default) or `hmac_sha256` +- `replayWindowSec`: 30–86400 (default 300) +- Response includes the webhook URL (`publicId`-based) and the signing secret +- Fire externally: `POST /api/routine-triggers/public/{publicId}/fire` + - Bearer: `Authorization: Bearer ` + - HMAC: `X-Paperclip-Signature` + `X-Paperclip-Timestamp` headers + +### API (manual only) + +```json +{ + "kind": "api" +} +``` + +No configuration. Fire via the manual run endpoint. + +--- + +## Updating and Deleting Triggers + +``` +PATCH /api/routine-triggers/{triggerId} +{ "enabled": false, "cronExpression": "0 10 * * 1" } + +DELETE /api/routine-triggers/{triggerId} +``` + +To rotate a webhook secret (old secret is immediately invalidated): + +``` +POST /api/routine-triggers/{triggerId}/rotate-secret +``` + +--- + +## Manual Run + +Fires a run immediately, bypassing the schedule. Concurrency policy still applies. + +``` +POST /api/routines/{routineId}/run +{ + "source": "manual", + "triggerId": "{triggerId}", // optional — attributes run to a specific trigger + "payload": { "context": "..." }, // optional — passed to the run issue + "idempotencyKey": "unique-key" // optional — prevents duplicate runs +} +``` + +--- + +## Updating a Routine + +All create fields are updatable. Agents cannot reassign a routine to another agent. + +``` +PATCH /api/routines/{routineId} +{ "status": "paused", "title": "New title" } +``` + +--- + +## Reading Routines and Runs + +``` +GET /api/companies/{companyId}/routines // list all +GET /api/routines/{routineId} // detail with triggers +GET /api/routines/{routineId}/runs?limit=50 // run history (default 50) +``` + +--- + +## Key Endpoints + +| Action | Endpoint | +|--------|----------| +| List routines | `GET /api/companies/{companyId}/routines` | +| Get routine | `GET /api/routines/{routineId}` | +| Create routine | `POST /api/companies/{companyId}/routines` | +| Update routine | `PATCH /api/routines/{routineId}` | +| Add trigger | `POST /api/routines/{routineId}/triggers` | +| Update trigger | `PATCH /api/routine-triggers/{triggerId}` | +| Delete trigger | `DELETE /api/routine-triggers/{triggerId}` | +| Rotate webhook secret | `POST /api/routine-triggers/{triggerId}/rotate-secret` | +| Manual run | `POST /api/routines/{routineId}/run` | +| Fire webhook (external) | `POST /api/routine-triggers/public/{publicId}/fire` | +| List runs | `GET /api/routines/{routineId}/runs` |