Merge pull request #2414 from aronprins/skill/routines

feat(skills): add paperclip-routines skill
This commit is contained in:
Dotta 2026-04-02 06:37:44 -05:00 committed by GitHub
commit 3db6bdfc3c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 259 additions and 4 deletions

View file

@ -3,9 +3,10 @@ name: paperclip
description: >
Interact with the Paperclip control plane API to manage tasks, coordinate with
other agents, and follow company governance. Use when you need to check
assignments, update task status, delegate work, post comments, or call any
Paperclip API endpoint. Do NOT use for the actual domain work itself (writing
code, research, etc.) — only for Paperclip coordination.
assignments, update task status, delegate work, post comments, set up or manage
routines (recurring scheduled tasks), or call any Paperclip API endpoint. Do NOT
use for the actual domain work itself (writing code, research, etc.) — only for
Paperclip coordination.
---
# Paperclip Skill
@ -137,6 +138,17 @@ Authorized managers can install company skills independently of hiring, then ass
If you are asked to install a skill for the company or an agent you MUST read:
`skills/paperclip/references/company-skills.md`
## 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.
- Create and manage routines with the routines API — agents can only manage routines assigned to themselves.
- Add triggers per routine: `schedule` (cron), `webhook`, or `api` (manual).
- Control concurrency and catch-up behaviour with `concurrencyPolicy` and `catchUpPolicy`.
If you are asked to create or manage routines you MUST read:
`skills/paperclip/references/routines.md`
## Critical Rules
- **Always checkout** before working. Never PATCH to `in_progress` manually.
@ -291,6 +303,17 @@ PATCH /api/agents/{agentId}/instructions-path
| List issue attachments | `GET /api/issues/:issueId/attachments` |
| Get attachment content | `GET /api/attachments/:attachmentId/content` |
| Delete attachment | `DELETE /api/attachments/:attachmentId` |
| 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` |
## Company Import / Export

View file

@ -597,7 +597,15 @@ Terminal states: `done`, `cancelled`
| GET | `/api/agents/me/inbox/mine?userId=:userId` | Mine-tab issue list for a specific board user |
| GET | `/api/agents/:agentId` | Agent details + chain of command |
| GET | `/api/companies/:companyId/agents` | List all agents in company |
| POST | `/api/companies/:companyId/agents` | Create agent directly (no approval) |
| PATCH | `/api/agents/:agentId` | Update agent config or budget |
| POST | `/api/agents/:agentId/pause` | Temporarily stop heartbeats |
| POST | `/api/agents/:agentId/resume` | Resume a paused agent |
| POST | `/api/agents/:agentId/terminate` | Permanently deactivate agent (irreversible) |
| POST | `/api/agents/:agentId/keys` | Create long-lived API key (full value shown once) |
| POST | `/api/agents/:agentId/heartbeat/invoke` | Manually trigger a heartbeat |
| GET | `/api/companies/:companyId/org` | Org chart tree |
| GET | `/api/companies/:companyId/adapters/:adapterType/models` | List selectable models for an adapter type |
| PATCH | `/api/agents/:agentId/instructions-path` | Set/clear instructions path (`AGENTS.md`) |
| GET | `/api/agents/:agentId/config-revisions` | List config revisions |
| POST | `/api/agents/:agentId/config-revisions/:revisionId/rollback` | Roll back config |
@ -608,6 +616,7 @@ Terminal states: `done`, `cancelled`
| ------ | ---------------------------------- | ---------------------------------------------------------------------------------------- |
| GET | `/api/companies/:companyId/issues` | List issues, sorted by priority. Filters: `?status=`, `?assigneeAgentId=`, `?assigneeUserId=`, `?projectId=`, `?labelId=`, `?q=` (full-text search across title, identifier, description, comments) |
| GET | `/api/issues/:issueId` | Issue details + ancestors |
| GET | `/api/issues/:issueId/heartbeat-context` | Compact context for heartbeat: issue state, ancestor summaries, comment cursor |
| POST | `/api/companies/:companyId/issues` | Create issue |
| PATCH | `/api/issues/:issueId` | Update issue (optional `comment` field adds a comment in same call) |
| POST | `/api/issues/:issueId/checkout` | Atomic checkout (claim + start). Idempotent if you already own it. |
@ -615,8 +624,13 @@ Terminal states: `done`, `cancelled`
| GET | `/api/issues/:issueId/comments` | List comments |
| GET | `/api/issues/:issueId/comments/:commentId` | Get a specific comment by ID |
| POST | `/api/issues/:issueId/comments` | Add comment (@-mentions trigger wakeups) |
| GET | `/api/issues/:issueId/documents` | List issue documents |
| GET | `/api/issues/:issueId/documents/:key` | Get issue document by key |
| PUT | `/api/issues/:issueId/documents/:key` | Create or update issue document (send `baseRevisionId` when updating) |
| GET | `/api/issues/:issueId/documents/:key/revisions` | Document revision history |
| DELETE | `/api/issues/:issueId/documents/:key` | Delete document (board-only) |
| GET | `/api/issues/:issueId/approvals` | List approvals linked to issue |
| POST | `/api/issues/:issueId/approvals` | Link approval to issue |
| POST | `/api/issues/:issueId/approvals` | Link approval to issue |
| DELETE | `/api/issues/:issueId/approvals/:approvalId` | Unlink approval from issue |
### Companies, Projects, Goals
@ -624,7 +638,11 @@ Terminal states: `done`, `cancelled`
| Method | Path | Description |
| ------ | ------------------------------------ | ------------------ |
| GET | `/api/companies` | List all companies |
| POST | `/api/companies` | Create company |
| GET | `/api/companies/:companyId` | Company details |
| PATCH | `/api/companies/:companyId` | Update company fields |
| POST | `/api/companies/:companyId/logo` | Upload company logo (multipart) |
| POST | `/api/companies/:companyId/archive` | Archive company |
| GET | `/api/companies/:companyId/projects` | List projects |
| GET | `/api/projects/:projectId` | Project details |
| POST | `/api/companies/:companyId/projects` | Create project (optional inline `workspace`) |
@ -639,6 +657,22 @@ Terminal states: `done`, `cancelled`
| PATCH | `/api/goals/:goalId` | Update goal |
| POST | `/api/companies/:companyId/openclaw/invite-prompt` | Generate OpenClaw invite prompt (CEO/board only) |
### Routines
| Method | Path | Description |
| ------ | ---- | ----------- |
| GET | `/api/companies/:companyId/routines` | List all routines in company |
| GET | `/api/routines/:routineId` | Routine details including triggers |
| POST | `/api/companies/:companyId/routines` | Create routine (`assigneeAgentId` + `projectId` required; agents: own only) |
| PATCH | `/api/routines/:routineId` | Update routine (agents: own only, cannot reassign) |
| POST | `/api/routines/:routineId/triggers` | Add trigger (`schedule`, `webhook`, or `api` kind) |
| PATCH | `/api/routine-triggers/:triggerId` | Update trigger (e.g. disable, change cron) |
| DELETE | `/api/routine-triggers/:triggerId` | Delete trigger |
| POST | `/api/routine-triggers/:triggerId/rotate-secret` | Rotate webhook signing secret (previous secret immediately invalidated) |
| POST | `/api/routines/:routineId/run` | Manual run (bypasses schedule; concurrency policy still applies) |
| POST | `/api/routine-triggers/public/:publicId/fire` | Fire webhook trigger from external system |
| GET | `/api/routines/:routineId/runs` | Run history (default 50) |
### Approvals, Costs, Activity, Dashboard
| Method | Path | Description |
@ -650,14 +684,25 @@ Terminal states: `done`, `cancelled`
| GET | `/api/approvals/:approvalId/issues` | Issues linked to approval |
| GET | `/api/approvals/:approvalId/comments` | Approval comments |
| POST | `/api/approvals/:approvalId/comments` | Add approval comment |
| POST | `/api/approvals/:approvalId/approve` | Approve approval request |
| POST | `/api/approvals/:approvalId/reject` | Reject approval request |
| POST | `/api/approvals/:approvalId/request-revision`| Board asks for revision |
| POST | `/api/approvals/:approvalId/resubmit` | Resubmit revised approval |
| POST | `/api/companies/:companyId/cost-events` | Report cost event |
| GET | `/api/companies/:companyId/costs/summary` | Company cost summary |
| GET | `/api/companies/:companyId/costs/by-agent` | Costs by agent |
| GET | `/api/companies/:companyId/costs/by-project` | Costs by project |
| GET | `/api/companies/:companyId/activity` | Activity log |
| GET | `/api/companies/:companyId/dashboard` | Company health summary |
### Secrets
| Method | Path | Description |
| ------ | ---- | ----------- |
| GET | `/api/companies/:companyId/secrets` | List secrets (metadata only) |
| POST | `/api/companies/:companyId/secrets` | Create secret |
| PATCH | `/api/secrets/:secretId` | Update secret value (creates new version) |
---
## Common Mistakes

View file

@ -0,0 +1,187 @@
# 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 or 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 |
| `description` | no | Human-readable description of the routine |
| `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 or 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, for example during 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.
All trigger kinds accept an optional `label` field (max 120 chars), which is useful for distinguishing multiple triggers of the same kind on one routine.
```
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 (for example `UTC` or `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 <secret>`
- 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 (the 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
GET /api/routines/{routineId}
GET /api/routines/{routineId}/runs?limit=50
```
Use the generic API endpoint tables in `skills/paperclip/references/api-reference.md` when you need a full cross-domain reference. Use this file when you need routine-specific behaviour, payload shape, or policy details.