From 8751ab5ad0324981a860798e34f218f49a2c8ed6 Mon Sep 17 00:00:00 2001 From: Dotta Date: Sun, 23 Aug 2026 08:34:48 +0000 Subject: [PATCH] chore(routines): sync lifecycle policy contracts Co-Authored-By: Paperclip --- doc/DATABASE.md | 2 +- doc/SPEC-implementation.md | 2 +- docs/api/routines.md | 15 +++++++++- docs/pipelines-tutorial.md | 1 + .../plugins/plugin-llm-wiki/src/manifest.ts | 3 ++ .../plugins/plugin-llm-wiki/src/worker.ts | 3 ++ packages/plugins/sdk/src/testing.ts | 2 ++ packages/shared/src/constants.ts | 4 +++ packages/shared/src/index.ts | 1 + .../shared/src/types/company-portability.ts | 1 + packages/shared/src/types/issue.ts | 2 ++ packages/shared/src/types/plugin.ts | 3 ++ .../src/validators/company-portability.ts | 1 + packages/shared/src/validators/plugin.ts | 2 ++ scripts/smoke/pipelines-tutorial-smoke.sh | 1 + .../routines/recent-agent-reflection.md | 1 + .../routines/refresh-stale-summaries.md | 1 + server/src/services/built-in-agents.ts | 10 ++++++- server/src/services/pipelines.ts | 2 ++ .../src/services/plugin-managed-routines.ts | 3 ++ skills/paperclip/SKILL.md | 2 +- skills/paperclip/references/routines.md | 20 +++++++++++++ ui/src/components/RoutineHistoryTab.test.tsx | 2 ++ .../components/routine-sections/context.tsx | 3 +- .../routine-sections/editable-sections.tsx | 29 +++++++++++++++++++ ui/src/lib/routine-run-display.test.ts | 9 ++++++ ui/src/lib/routine-run-display.ts | 8 ++++- ui/src/lib/workspace-routines.test.ts | 1 + ui/src/pages/Routines.test.tsx | 2 ++ .../stories/routine-detail-c.stories.tsx | 2 ++ .../stories/routine-secrets.stories.tsx | 2 ++ 31 files changed, 133 insertions(+), 7 deletions(-) diff --git a/doc/DATABASE.md b/doc/DATABASE.md index 7adb43eb295..8871a876223 100644 --- a/doc/DATABASE.md +++ b/doc/DATABASE.md @@ -233,7 +233,7 @@ the reusable company-level slot, declarations record where `user_secret_ref` bindings are required, and the concrete value is selected later for the responsible user. -Secret-aware env bindings are supported by agents, projects, and routines. Routine env lives in `routines.env`, is captured in `routine_revisions.snapshot`, and routine dispatches store `routine_runs.routine_revision_id` so runtime secret resolution uses the env snapshot that existed when the run was created. Routine secret refs bind with `target_type = 'routine'`, `target_id = routines.id`, and `config_path` values under `env.*`. +Secret-aware env bindings are supported by agents, projects, and routines. Routine env lives in `routines.env`, is captured in `routine_revisions.snapshot`, and routine dispatches store `routine_runs.routine_revision_id` so runtime secret resolution uses the env snapshot that existed when the run was created. The same immutable snapshot supplies `lifecycle_policy`; `latest_success_wins` records supersession with `issues.superseded_by_issue_id`, `routine_runs.status = 'superseded'`, and `routine_runs.superseded_by_run_id`, except that an older instance with any transitively non-terminal `issues.parent_id` descendant is left unchanged and receives an idempotent `issue.supersession_deferred` activity entry with the superseding issue/run and live descendant issue ids. Routine secret refs bind with `target_type = 'routine'`, `target_id = routines.id`, and `config_path` values under `env.*`. For local/default installs, the active provider is `local_encrypted`: diff --git a/doc/SPEC-implementation.md b/doc/SPEC-implementation.md index 5a4984b1bb4..f3aa4ce9fa7 100644 --- a/doc/SPEC-implementation.md +++ b/doc/SPEC-implementation.md @@ -457,7 +457,7 @@ The current implementation includes additional V1-control-plane tables beyond th - Issue structure and review: `issue_relations` for blockers, `labels`/`issue_labels`, `issue_thread_interactions`, `issue_approvals`, `issue_execution_decisions`, `issue_work_products`, `issue_inbox_archives`, `issue_read_states`, and issue reference mention indexes. - Execution and workspace control: `execution_workspaces`, `project_workspaces`, `workspace_runtime_services`, `workspace_operations`, `environments`, `environment_leases`, `agent_task_sessions`, `agent_runtime_state`, `agent_wakeup_requests`, heartbeat events, and watchdog decision tables. -- Plugins and routines: `plugins`, plugin config/state/entities/jobs/logs/webhooks, plugin database namespaces/migrations, plugin company settings, `routines`, `routine_revisions`, `routine_triggers`, and `routine_runs`. +- Plugins and routines: `plugins`, plugin config/state/entities/jobs/logs/webhooks, plugin database namespaces/migrations, plugin company settings, `routines`, `routine_revisions`, `routine_triggers`, and `routine_runs`. Routine concurrency is a dispatch-time policy. Instance lifecycle is a separate, revision-snapshotted policy with safe default `independent`; opt-in `latest_success_wins` lets a newer successful execution cancel and link older visible non-terminal instances without making pause or archive destructive. An older instance with any transitively non-terminal `parent_id` descendant is skipped, leaving its issue and routine run unchanged, and records one `issue.supersession_deferred` activity entry per superseding run with the superseding issue/run ids and the live descendant issue ids. - Access and operations: company memberships, instance roles, principal permission grants, invites, join requests, board API keys, CLI auth challenges, budget policies/incidents, feedback exports/votes, company skills, sidebar preferences, and company logos. Decision-desk triage uses company-scoped sidecars rather than adding queue fields to every attention source: diff --git a/docs/api/routines.md b/docs/api/routines.md index 2ad279a74fd..d0f5bbcb9b1 100644 --- a/docs/api/routines.md +++ b/docs/api/routines.md @@ -34,6 +34,7 @@ POST /api/companies/{companyId}/routines "priority": "medium", "status": "active", "concurrencyPolicy": "coalesce_if_active", + "lifecyclePolicy": "independent", "catchUpPolicy": "skip_missed" } ``` @@ -53,6 +54,7 @@ Fields: | `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 | +| `lifecyclePolicy` | no | Whether a later successful execution supersedes older unfinished executions | | `catchUpPolicy` | no | Behaviour for missed scheduled runs | **Concurrency policies:** @@ -63,6 +65,17 @@ Fields: | `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 | +Concurrency is a dispatch-time rule. It determines whether a trigger creates a new execution while another execution is active. + +**Instance lifecycle policies:** + +| Value | Behaviour | +|-------|-----------| +| `independent` (default) | Every execution remains independent, including older unfinished executions after a newer success | +| `latest_success_wins` | After a newer execution reaches `done`, cancel and link older visible non-terminal executions that have no non-terminal descendants | + +Lifecycle is a completion-time rule. It does not change trigger dispatch, and it is never inferred from `concurrencyPolicy`. Failed, blocked, or cancelled newer executions do not supersede anything. Pausing or archiving a routine also leaves existing executions untouched. If an older instance has any transitively non-terminal descendant through `parentId`, Paperclip leaves that instance and its routine run unchanged so delegated work stays owned and inspectable. + **Catch-up policies:** | Value | Behaviour | @@ -192,7 +205,7 @@ Fires a webhook trigger from an external system. Requires a valid `Authorization GET /api/routines/{routineId}/runs?limit=50 ``` -Returns recent run history for the routine. Defaults to 50 most recent runs. +Returns recent run history for the routine. Defaults to 50 most recent runs. Superseded runs have status `superseded` and `supersededByRunId`; their execution issues expose `supersededByIssueId`. ## Agent Access Rules diff --git a/docs/pipelines-tutorial.md b/docs/pipelines-tutorial.md index e7fc69a12c6..2baedc1751e 100644 --- a/docs/pipelines-tutorial.md +++ b/docs/pipelines-tutorial.md @@ -189,6 +189,7 @@ cat > /tmp/drafting-routine.json <, triggers: Array<{ kind: string; @@ -1182,6 +1185,7 @@ export function builtInAgentService(db: Db) { description: routine.description ?? "", priority: routine.priority, concurrencyPolicy: routine.concurrencyPolicy, + lifecyclePolicy: routine.lifecyclePolicy, catchUpPolicy: routine.catchUpPolicy, variables: normalizeRoutineVariablesForHash(routine), triggers: normalizeRoutineTriggersForHash(triggers), @@ -1293,6 +1297,7 @@ export function builtInAgentService(db: Db) { priority: routine.priority, status: routine.status, concurrencyPolicy: routine.concurrencyPolicy, + lifecyclePolicy: routine.lifecyclePolicy, catchUpPolicy: routine.catchUpPolicy, variables: routine.variables, }, actor) @@ -1303,6 +1308,7 @@ export function builtInAgentService(db: Db) { priority: routine.priority, status: routine.status, concurrencyPolicy: routine.concurrencyPolicy, + lifecyclePolicy: routine.lifecyclePolicy, catchUpPolicy: routine.catchUpPolicy, variables: routine.variables, }, actor); @@ -1364,6 +1370,7 @@ export function builtInAgentService(db: Db) { description: routine.description ?? "", priority: routine.priority as "critical" | "high" | "medium" | "low", concurrencyPolicy: routine.concurrencyPolicy as "always_enqueue" | "coalesce_if_active" | "skip_if_active", + lifecyclePolicy: routine.lifecyclePolicy as "independent" | "latest_success_wins", catchUpPolicy: routine.catchUpPolicy as "enqueue_missed_with_cap" | "skip_missed", variables: routine.variables ?? [], }, triggers) : null; @@ -1439,6 +1446,7 @@ export function builtInAgentService(db: Db) { description: routine.description ?? "", priority: routine.priority as "critical" | "high" | "medium" | "low", concurrencyPolicy: routine.concurrencyPolicy as "always_enqueue" | "coalesce_if_active" | "skip_if_active", + lifecyclePolicy: routine.lifecyclePolicy as "independent" | "latest_success_wins", catchUpPolicy: routine.catchUpPolicy as "enqueue_missed_with_cap" | "skip_missed", variables: routine.variables ?? [], }, triggers) : null; diff --git a/server/src/services/pipelines.ts b/server/src/services/pipelines.ts index dfe2bb43f40..91e71477b35 100644 --- a/server/src/services/pipelines.ts +++ b/server/src/services/pipelines.ts @@ -1174,6 +1174,7 @@ function routineRevisionSnapshotRoutine(routine: typeof routines.$inferSelect): priority: routine.priority as RoutineRevisionSnapshotV1["routine"]["priority"], status: routine.status as RoutineRevisionSnapshotV1["routine"]["status"], concurrencyPolicy: routine.concurrencyPolicy as RoutineRevisionSnapshotV1["routine"]["concurrencyPolicy"], + lifecyclePolicy: routine.lifecyclePolicy as RoutineRevisionSnapshotV1["routine"]["lifecyclePolicy"], catchUpPolicy: routine.catchUpPolicy as RoutineRevisionSnapshotV1["routine"]["catchUpPolicy"], activityGatePolicy: routine.activityGatePolicy as RoutineRevisionSnapshotV1["routine"]["activityGatePolicy"], activityGateScope: routine.activityGateScope as RoutineRevisionSnapshotV1["routine"]["activityGateScope"], @@ -2802,6 +2803,7 @@ export function pipelineService(db: Db, deps: { heartbeat?: IssueAssignmentWakeu status: "active", priority: "medium", concurrencyPolicy: "coalesce_if_active", + lifecyclePolicy: "independent", catchUpPolicy: "skip_missed", originKind: "pipeline_automation", originId: input.pipelineId, diff --git a/server/src/services/plugin-managed-routines.ts b/server/src/services/plugin-managed-routines.ts index f91983c6217..e08b7cce67f 100644 --- a/server/src/services/plugin-managed-routines.ts +++ b/server/src/services/plugin-managed-routines.ts @@ -48,6 +48,7 @@ function buildRoutineDefaults(declaration: PluginManagedRoutineDeclaration) { status: declaration.status ?? null, priority: declaration.priority ?? "medium", concurrencyPolicy: declaration.concurrencyPolicy ?? "coalesce_if_active", + lifecyclePolicy: declaration.lifecyclePolicy ?? "independent", catchUpPolicy: declaration.catchUpPolicy ?? "skip_missed", activityGatePolicy: declaration.activityGatePolicy ?? "always", activityGateScope: declaration.activityGateScope ?? "company", @@ -371,6 +372,7 @@ export function pluginManagedRoutineService( priority: declaration.priority ?? "medium", status: declaration.status ?? (refs.assigneeAgentId ? "active" : "paused"), concurrencyPolicy: declaration.concurrencyPolicy ?? "coalesce_if_active", + lifecyclePolicy: declaration.lifecyclePolicy ?? "independent", catchUpPolicy: declaration.catchUpPolicy ?? "skip_missed", activityGatePolicy: declaration.activityGatePolicy ?? "always", activityGateScope: declaration.activityGateScope ?? "company", @@ -433,6 +435,7 @@ export function pluginManagedRoutineService( priority: declaration.priority ?? "medium", status: declaration.status ?? (refs.assigneeAgentId ? "active" : "paused"), concurrencyPolicy: declaration.concurrencyPolicy ?? "coalesce_if_active", + lifecyclePolicy: declaration.lifecyclePolicy ?? "independent", catchUpPolicy: declaration.catchUpPolicy ?? "skip_missed", activityGatePolicy: declaration.activityGatePolicy ?? "always", activityGateScope: declaration.activityGateScope ?? "company", diff --git a/skills/paperclip/SKILL.md b/skills/paperclip/SKILL.md index 33aeceda407..d3d9bcc7ead 100644 --- a/skills/paperclip/SKILL.md +++ b/skills/paperclip/SKILL.md @@ -438,7 +438,7 @@ Routines are recurring tasks. Each time a routine fires it creates an execution - 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`. +- Control trigger-time concurrency, success-time instance lifecycle, and catch-up behaviour with `concurrencyPolicy`, `lifecyclePolicy`, and `catchUpPolicy`. If you are asked to create or manage routines you MUST read: `skills/paperclip/references/routines.md` diff --git a/skills/paperclip/references/routines.md b/skills/paperclip/references/routines.md index 253999aa741..2224f6a049c 100644 --- a/skills/paperclip/references/routines.md +++ b/skills/paperclip/references/routines.md @@ -6,6 +6,7 @@ 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) +- An instance lifecycle policy (what a later successful run does to older unfinished instances) - A catch-up policy (what to do with missed scheduled runs) - An activity gate policy (whether quiet scheduled ticks should be skipped) @@ -38,6 +39,7 @@ POST /api/companies/{companyId}/routines "priority": "medium", "status": "active", "concurrencyPolicy": "coalesce_if_active", + "lifecyclePolicy": "independent", "catchUpPolicy": "skip_missed", "activityGatePolicy": "always", "activityGateScope": "company" @@ -55,6 +57,7 @@ POST /api/companies/{companyId}/routines | `priority` | no | `critical` `high` `medium` (default) `low` | | `status` | no | `active` (default) `paused` `archived` | | `concurrencyPolicy` | no | See below | +| `lifecyclePolicy` | no | See below | | `catchUpPolicy` | no | See below | | `activityGatePolicy` | no | `always` (default) or `require_external_activity`; see below | | `activityGateScope` | no | `company` (default) or `project`; see below | @@ -71,6 +74,23 @@ Controls what happens when a trigger fires while the previous run issue is still | `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 | +Concurrency applies when a trigger arrives. It does not decide whether one completed execution subsumes another. + +--- + +## Instance Lifecycle Policies + +Controls what happens to older execution instances after a newer instance succeeds. + +| Policy | Behaviour | +|--------|-----------| +| `independent` **(default)** | Keep every execution independent, even after a newer run succeeds | +| `latest_success_wins` | When a newer execution issue reaches `done`, cancel and link each older visible non-terminal execution that has no non-terminal descendants | + +The run's dispatch-time routine revision supplies this policy. Paperclip never infers it from `concurrencyPolicy`. A failed, blocked, or cancelled newer execution does not supersede older work. Pausing or archiving a routine does not cancel existing execution issues. Paperclip recursively follows `parentId`; if any descendant is non-terminal, it skips that older instance and leaves both its issue and routine run unchanged. + +Superseded execution issues expose `supersededByIssueId`. Their routine runs have terminal status `superseded` and expose `supersededByRunId`, so history retains both sides of the link. + --- ## Catch-Up Policies diff --git a/ui/src/components/RoutineHistoryTab.test.tsx b/ui/src/components/RoutineHistoryTab.test.tsx index cde960d13d4..2bab56e931b 100644 --- a/ui/src/components/RoutineHistoryTab.test.tsx +++ b/ui/src/components/RoutineHistoryTab.test.tsx @@ -96,6 +96,7 @@ function snapshotV1(overrides?: Partial): priority: "medium", status: "active", concurrencyPolicy: "coalesce_if_active", + lifecyclePolicy: "independent", catchUpPolicy: "skip_missed", activityGatePolicy: "always", activityGateScope: "company", @@ -140,6 +141,7 @@ function createRoutine(overrides: Partial = {}): Routine { priority: "medium", status: "active", concurrencyPolicy: "coalesce_if_active", + lifecyclePolicy: "independent", catchUpPolicy: "skip_missed", activityGatePolicy: "always", activityGateScope: "company", diff --git a/ui/src/components/routine-sections/context.tsx b/ui/src/components/routine-sections/context.tsx index 6920ea45a69..39fbef783db 100644 --- a/ui/src/components/routine-sections/context.tsx +++ b/ui/src/components/routine-sections/context.tsx @@ -45,7 +45,7 @@ export const SECTION_FIELD_KEYS: Record = { overview: ["title", "description", "projectId", "assigneeAgentId", "priority"], variables: ["variables"], secrets: ["env"], - delivery: ["concurrencyPolicy", "catchUpPolicy", "activityGatePolicy", "activityGateScope"], + delivery: ["concurrencyPolicy", "lifecyclePolicy", "catchUpPolicy", "activityGatePolicy", "activityGateScope"], }; export type RoutineEditDraft = { @@ -55,6 +55,7 @@ export type RoutineEditDraft = { assigneeAgentId: string; priority: string; concurrencyPolicy: string; + lifecyclePolicy: string; catchUpPolicy: string; activityGatePolicy: string; activityGateScope: string; diff --git a/ui/src/components/routine-sections/editable-sections.tsx b/ui/src/components/routine-sections/editable-sections.tsx index 49e79c2522c..164e644cf78 100644 --- a/ui/src/components/routine-sections/editable-sections.tsx +++ b/ui/src/components/routine-sections/editable-sections.tsx @@ -55,6 +55,19 @@ const concurrencyPolicyOptions = [ }, ]; +const lifecyclePolicyOptions = [ + { + value: "independent", + title: "Independent", + description: "Keep every execution instance separate, regardless of later results.", + }, + { + value: "latest_success_wins", + title: "Latest success wins", + description: "When a newer instance succeeds, cancel older unfinished instances unless they own live descendant work.", + }, +]; + const catchUpPolicyOptions = [ { value: "skip_missed", @@ -713,6 +726,22 @@ export function DeliverySection() { options={concurrencyPolicyOptions} /> +
+

+ Instance lifecycle +

+ + setEditDraft((current) => ({ ...current, lifecyclePolicy })) + } + options={lifecyclePolicyOptions} + /> +

+ Lifecycle applies after a successful run. Concurrency controls what happens when a trigger arrives. +

+

Catch-up diff --git a/ui/src/lib/routine-run-display.test.ts b/ui/src/lib/routine-run-display.test.ts index ada4481965f..bd9f25f3057 100644 --- a/ui/src/lib/routine-run-display.test.ts +++ b/ui/src/lib/routine-run-display.test.ts @@ -36,6 +36,15 @@ describe("runRowSubtitle", () => { expect(subtitle).toBe("Cron timed out"); }); + it("links superseded runs to the newer successful run", () => { + expect(runRowSubtitle({ + status: "superseded", + supersededByRunId: "12345678-1234-4123-8123-123456789abc", + failureReason: null, + triggerPayload: null, + }, variables)).toBe("Superseded by run 12345678"); + }); + it("falls back to a generic label when a failed run has no reason", () => { const subtitle = runRowSubtitle( { status: "failed", failureReason: null, triggerPayload: null }, diff --git a/ui/src/lib/routine-run-display.ts b/ui/src/lib/routine-run-display.ts index e05b0da007f..2d694731eec 100644 --- a/ui/src/lib/routine-run-display.ts +++ b/ui/src/lib/routine-run-display.ts @@ -50,9 +50,15 @@ const SKIP_REASON_LABELS: Record = { * Returns an empty string when there is nothing meaningful to show. */ export function runRowSubtitle( - run: Pick, + run: Pick + & Partial>, variables: readonly RoutineVariable[] | null | undefined, ): string { + if (run.status === "superseded") { + return run.supersededByRunId + ? `Superseded by run ${run.supersededByRunId.slice(0, 8)}` + : "Superseded by a newer successful run"; + } if (run.status === "failed") { return run.failureReason?.trim() || "Run failed"; } diff --git a/ui/src/lib/workspace-routines.test.ts b/ui/src/lib/workspace-routines.test.ts index e5ef480fd9d..9e97fe0486c 100644 --- a/ui/src/lib/workspace-routines.test.ts +++ b/ui/src/lib/workspace-routines.test.ts @@ -20,6 +20,7 @@ function createRoutine(overrides: Partial = {}): RoutineListIte priority: "medium", status: "active", concurrencyPolicy: "coalesce_if_active", + lifecyclePolicy: "independent", catchUpPolicy: "skip_missed", activityGatePolicy: "always", activityGateScope: "company", diff --git a/ui/src/pages/Routines.test.tsx b/ui/src/pages/Routines.test.tsx index fdcea31bef1..4c81c385423 100644 --- a/ui/src/pages/Routines.test.tsx +++ b/ui/src/pages/Routines.test.tsx @@ -298,6 +298,7 @@ function createRoutine(overrides: Partial): RoutineListItem { priority: "medium", status: "active", concurrencyPolicy: "coalesce_if_active", + lifecyclePolicy: "independent", catchUpPolicy: "skip_missed", activityGatePolicy: "always", activityGateScope: "company", @@ -537,6 +538,7 @@ describe("Routines page", () => { dispatchFingerprint: null, linkedIssueId: null, coalescedIntoRunId: null, + supersededByRunId: null, failureReason: null, completedAt: null, createdAt: new Date("2026-04-02T00:00:00.000Z"), diff --git a/ui/storybook/stories/routine-detail-c.stories.tsx b/ui/storybook/stories/routine-detail-c.stories.tsx index d85a6924b66..da3e9c2438a 100644 --- a/ui/storybook/stories/routine-detail-c.stories.tsx +++ b/ui/storybook/stories/routine-detail-c.stories.tsx @@ -86,6 +86,7 @@ const routine: RoutineDetailType = { priority: "medium", status: "active", concurrencyPolicy: "coalesce_if_active", + lifecyclePolicy: "independent", catchUpPolicy: "skip_missed", activityGatePolicy: "always", activityGateScope: "company", @@ -251,6 +252,7 @@ function makeContext( assigneeAgentId: routineDetail.assigneeAgentId ?? "", priority: routineDetail.priority, concurrencyPolicy: routineDetail.concurrencyPolicy, + lifecyclePolicy: routineDetail.lifecyclePolicy, catchUpPolicy: routineDetail.catchUpPolicy, activityGatePolicy: "always", activityGateScope: "company", diff --git a/ui/storybook/stories/routine-secrets.stories.tsx b/ui/storybook/stories/routine-secrets.stories.tsx index 42bf771b2ba..402d2883093 100644 --- a/ui/storybook/stories/routine-secrets.stories.tsx +++ b/ui/storybook/stories/routine-secrets.stories.tsx @@ -156,6 +156,7 @@ function makeSnapshot(env: RoutineEnvConfig | null): RoutineRevisionSnapshotV1 { priority: "medium", status: "active", concurrencyPolicy: "coalesce_if_active", + lifecyclePolicy: "independent", catchUpPolicy: "skip_missed", activityGatePolicy: "always", activityGateScope: "company", @@ -180,6 +181,7 @@ function makeRoutine(latestRevisionId: string, latestRevisionNumber: number): Ro priority: "medium", status: "active", concurrencyPolicy: "coalesce_if_active", + lifecyclePolicy: "independent", catchUpPolicy: "skip_missed", activityGatePolicy: "always", activityGateScope: "company",