- Add missing `description` field to the Creating a Routine field table - Document optional `label` field available on all trigger kinds
213 lines
5.9 KiB
Markdown
213 lines
5.9 KiB
Markdown
---
|
||
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 |
|
||
| `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/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.
|
||
|
||
All trigger kinds accept an optional `label` field (max 120 chars) — 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 (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 <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 (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` |
|