## Thinking Path > - Paperclip is the control plane for autonomous AI companies. > - Routines are the scheduled/recurring work surface that keeps a company operating without manual kicks. > - Operators need routine edits to be auditable and recoverable, especially when routines control assignments, prompts, triggers, and webhook secrets. > - Documents already have revision-style safety, but routines did not have equivalent history or restore semantics. > - This pull request adds append-only routine revisions across the database, shared contracts, server routes, and board UI. > - The benefit is safer routine iteration: users can inspect history, compare changes, restore older definitions, and avoid overwriting newer edits. ## What Changed - Added `routine_revisions` storage, latest revision pointers on routines, shared types, validators, and API docs for routine revision history. - Added server service/route support for listing routine revisions, conflict-aware routine saves, and append-only restore operations. - Added a History tab on routine detail with revision preview, structured change summaries, description line diffs, dirty-edit blocking, restore confirmation, and restored webhook secret surfacing. - Extracted the line diff helper from `DocumentDiffModal` into `ui/src/lib/line-diff.ts` for reuse. - Rebased the branch onto current `public-gh/master` and renumbered the routine revision migration to `0077_unusual_karnak` after upstream `0076_useful_elektra`. - Made the `0077` routine revision migration idempotent so installs that already applied the branch-local `0076_unusual_karnak` can safely advance. - Updated the plugin SDK test harness routine fixture with the new revision fields required by the shared `Routine` contract. ## Verification - `pnpm --filter @paperclipai/db run check:migrations` passed. - `pnpm exec vitest run --project @paperclipai/shared packages/shared/src/validators/routine.test.ts` passed. - `pnpm exec vitest run --project @paperclipai/ui ui/src/lib/line-diff.test.ts ui/src/components/RoutineHistoryTab.test.tsx ui/src/lib/workspace-routines.test.ts ui/src/pages/Routines.test.tsx` passed. - `pnpm exec vitest run --project @paperclipai/server server/src/__tests__/routines-service.test.ts --pool=forks --poolOptions.forks.isolate=true` passed. - `pnpm exec vitest run --project @paperclipai/server server/src/__tests__/routines-routes.test.ts --pool=forks --poolOptions.forks.isolate=true` passed. - `pnpm --filter @paperclipai/plugin-sdk typecheck` passed after updating the SDK test harness fixture. - `pnpm --filter @paperclipai/plugin-sdk build` passed; this refreshed local generated SDK output needed by plugin example typechecks. - `pnpm -r typecheck` passed. ## Risks - Medium migration risk: this adds routine revision storage and backfills existing routines. The migration is ordered after upstream `0076` and uses `IF NOT EXISTS` / duplicate-object guards to tolerate earlier branch-local migration application. - Restore behavior intentionally appends a new revision instead of mutating history; callers expecting an in-place rollback need to follow the new latest revision pointer. - Restoring webhook triggers recreates webhook secret material, so users must copy newly surfaced secrets after restore. - Conflict-aware saves now reject stale routine edits when the client sends an older `baseRevisionId`. > For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and discuss it in `#dev` before opening the PR. Feature PRs that overlap with planned core work may need to be redirected — check the roadmap first. See `CONTRIBUTING.md`. ## Model Used - OpenAI Codex, GPT-5-based coding agent, with shell/tool use in a local git worktree. Exact context-window size is not exposed in this runtime. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] If this change affects the UI, I have included before/after screenshots - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] I will address all Greptile and reviewer comments before requesting merge Screenshots: not attached in this draft PR; the new UI flow is covered by component tests listed above. --------- Co-authored-by: Paperclip <noreply@paperclip.ing>
5.8 KiB
title, summary
| title | summary |
|---|---|
| Routines | Recurring task scheduling, triggers, and run history |
Routines are recurring tasks that fire on a schedule, webhook, or API call and create a heartbeat run for the assigned agent.
List Routines
GET /api/companies/{companyId}/routines
Returns all routines in the company.
Get Routine
GET /api/routines/{routineId}
Returns routine details including triggers.
Create Routine
POST /api/companies/{companyId}/routines
{
"title": "Weekly CEO briefing",
"description": "Compile status report and email Founder",
"assigneeAgentId": "{agentId}",
"projectId": "{projectId}",
"goalId": "{goalId}",
"priority": "medium",
"status": "active",
"concurrencyPolicy": "coalesce_if_active",
"catchUpPolicy": "skip_missed"
}
Agents can only create routines assigned to themselves. Board operators can assign to any agent.
Fields:
| Field | Required | Description |
|---|---|---|
title |
yes | Routine name |
description |
no | Human-readable description of the routine |
assigneeAgentId |
yes | Agent who receives each run |
projectId |
yes | Project this routine belongs to |
goalId |
no | Goal to link runs to |
parentIssueId |
no | Parent issue for created run issues |
priority |
no | critical, high, medium (default), low |
status |
no | active (default), paused, archived |
concurrencyPolicy |
no | Behaviour when a run fires while a previous one is still active |
catchUpPolicy |
no | Behaviour for missed scheduled runs |
Concurrency policies:
| Value | Behaviour |
|---|---|
coalesce_if_active (default) |
Incoming run is immediately finalised as coalesced and linked to the active run — no new issue is created |
skip_if_active |
Incoming run is immediately finalised as skipped and linked to the active run — no new issue is created |
always_enqueue |
Always create a new run regardless of active runs |
Catch-up policies:
| Value | Behaviour |
|---|---|
skip_missed (default) |
Missed scheduled runs are dropped |
enqueue_missed_with_cap |
Missed runs are enqueued up to an internal cap |
Update Routine
PATCH /api/routines/{routineId}
{
"status": "paused",
"baseRevisionId": "{latestRevisionId}"
}
All fields from create are updatable. baseRevisionId is optional for backward compatibility; when provided, stale values return 409 Conflict with the current revision id. Agents can only update routines assigned to themselves and cannot reassign a routine to another agent.
List Revisions
GET /api/routines/{routineId}/revisions
Returns append-only routine definition revisions newest first. Snapshots include routine fields and safe trigger metadata only; webhook secret values and secretId are never returned.
Restore Revision
POST /api/routines/{routineId}/revisions/{revisionId}/restore
Restores a historical routine definition by creating a new latest revision copied from the selected revision. Historical revision rows, routine run history, and activity history are preserved. If restoring a deleted webhook trigger requires recreating it, the response can include one-time replacement secret material for that trigger.
Add Trigger
POST /api/routines/{routineId}/triggers
Three trigger kinds:
Schedule — fires on a cron expression:
{
"kind": "schedule",
"cronExpression": "0 9 * * 1",
"timezone": "Europe/Amsterdam"
}
Webhook — fires on an inbound HTTP POST to a generated URL:
{
"kind": "webhook",
"signingMode": "hmac_sha256",
"replayWindowSec": 300
}
Signing modes: bearer (default), hmac_sha256. Replay window range: 30–86400 seconds (default 300).
API — fires only when called explicitly via Manual Run:
{
"kind": "api"
}
A routine can have multiple triggers of different kinds.
Update Trigger
PATCH /api/routine-triggers/{triggerId}
{
"enabled": false,
"cronExpression": "0 10 * * 1"
}
Delete Trigger
DELETE /api/routine-triggers/{triggerId}
Rotate Trigger Secret
POST /api/routine-triggers/{triggerId}/rotate-secret
Generates a new signing secret for webhook triggers. The previous secret is immediately invalidated.
Manual Run
POST /api/routines/{routineId}/run
{
"source": "manual",
"triggerId": "{triggerId}",
"payload": { "context": "..." },
"idempotencyKey": "my-unique-key"
}
Fires a run immediately, bypassing the schedule. Concurrency policy still applies.
triggerId is optional. When supplied, the server validates the trigger belongs to this routine (403) and is enabled (409), then records the run against that trigger and updates its lastFiredAt. Omit it for a generic manual run with no trigger attribution.
Fire Public Trigger
POST /api/routine-triggers/public/{publicId}/fire
Fires a webhook trigger from an external system. Requires a valid Authorization or X-Paperclip-Signature + X-Paperclip-Timestamp header pair matching the trigger's signing mode.
List Runs
GET /api/routines/{routineId}/runs?limit=50
Returns recent run history for the routine. Defaults to 50 most recent runs.
Agent Access Rules
Agents can read all routines in their company but can only create and manage routines assigned to themselves:
| Operation | Agent | Board |
|---|---|---|
| List / Get | ✅ any routine | ✅ |
| Create | ✅ own only | ✅ |
| Update / activate | ✅ own only | ✅ |
| Add / update / delete triggers | ✅ own only | ✅ |
| Rotate trigger secret | ✅ own only | ✅ |
| Manual run | ✅ own only | ✅ |
| Reassign to another agent | ❌ | ✅ |
Routine Lifecycle
active -> paused -> active
-> archived
Archived routines do not fire and cannot be reactivated.